链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。  链表由一系列节点(链表中的每一个元素称为节点)组成,节点可以再运行时动态生成,克服了数组需要预先知道数据量大小的缺点。每个节点包括两个部分一个是存储数据元素的数据域,另一个时存储相邻节点地址的指针域。

 特性

 根据指针域的不同,链表可分为单向链表和双向链表,单向链表中各节点只记录后继元素的地址,双向链表的节点会记录前驱、后继两个节点的地址。  相较于线性表的顺序结构,链表操作复杂。由于不必按顺序存储,链表在插入的时候可以达到O ( 1 )的复杂度,比线性表快得多,但是查找一个节点或者访问特定编号的节点则需要O ( n )的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O ( 1 )。

 物理结构  物理结构

 链表使用链式存储方式存储数据,便于插入和删除,遍历方面可以通过ST表等进行优化。竞赛中链表的插入和删除操作顺序时常考点。

例题: 例1 向一个栈顶指针为hs 的链式栈中插入一个指针s指向的节点时,应执行( )。 A.hs->next = s ; B.s->next = hs ; hs = s ; C.s->next = hs->next ; hs->next = s ; D.s->next = hs ; hs = hs->next ; 【正确答案】b 解析:为栈顶元素next指针赋值就是入栈操作,入栈后应记得更新栈顶指针。

例2 双向链表中有两个指针域llink和rlink,分别指向前驱与后继。设p指向链表中的一个节点,q指向一待插入节点,现要求在p前插入q,则正确的插入为( )。

A.p->llink=q;q->rlink=p;p->link->rlink=q;q->llink =p->llink;

B.g->llink=p->llink;p->llink->rlink=q;q->rlink=p;p->llink =q->rlink;

C.q->rlink=p;p->rlink=q;p->llink->rlink=q;q->rlink=p;

D.p->llink->rlink=q;q->rlink=p;q->llink=p->llink;p->llink =q;

**【正确答案】D

解析:插入新节点时,需要注意改变指针指向时不要弄丢节点(p->llink)。**

例3 链表不具有的特点是( )。

A.可随机访问任一元素。

B.不必事先估计存储空间。

C.插入和删除不需要移动元素。

D.所需空间与线性表长度成正比。

【正确答案】A 解析:根据链表的概念可知。

例4 从一个具有 n 个结点的单链表中查找其值等于 x 结点时,在查找成功的情况下,需平均比较( )个结点。 A.n B.n/2 C.(n-1)/2 D.(n+1)/2 **【正确答案】D

解析:n=1和n=n,1+n的和除以2

链表的代码

#include<bits/stdc++.h> 
using namespace std;
//创建和遍历链表3
//指针、结构体(创建) 
struct Node{
	int data;//值域 
	Node *next;//指针域,指向下一个 
};
//继续创建三个指针 指向链表的头head,指向链表尾 tail 
//在链表中游走的指针 p 
Node *head,*tail,*p; 
int x;
void inst(int key,int pos); 
void del(int data); 
int main(){
	//创建链表,不停增加结点,直到输入的值为-1时,输出链表
	cin>>x; 
	//声明一个结点,因为是第一个声明的结点,所以必须是头结点head 
	head = new Node;// 产生一个Node结点
	tail = head;//因为初始结点,头和尾结点都是一个 
	while(x!=-1)
	{
		p=new Node;//创建结点,将p指向这个新建的结点
		p->data = x; 
		tail->next = p; 
		tail = p;
		tail->next = NULL;//最后一个结点的尾部没有了
		cin>>x; 
	}
	del(8);//调用删除方法 
	inst(21,4);//调用插入方法 
	//遍历链表输出
	if(head->next!=NULL){ 
		p = head->next; 
		while(p->next!=NULL) 
		{
			cout<<p->data<<" ";
			p=p->next;//将p移到下一个结点
		}
		cout<<p->data;//因为前面输出的时候跳过了一个结点 
	}else{
		cout<<"空链表";
	}
	return 0;
} 

//链表的删除  
void del(int key){
	Node *pre;//寻找符合条件结点的前驱
	p=head;pre=p;
	//判断p是否已经移到尾结点	
	while(p->next!=NULL) {
		if(p->data==key){
			//符合条件的结点p的前驱结点pre
			//pre的后继指向p的后继		 
			pre->next=p->next;
			delete p;//释放p所指向的结点
			p=pre->next;	
		}else{//当前的结点值不为key,指针p继续往前走 
			pre=p; 
			p=p->next;
		}
	}
	//while 循环完成之后,p指向的是最后一个结点,需要单独拿出来处理	
	if(p->data==key){
		delete p;//删除最后一个结点后,pre就是最后一个结点了 
		pre->next=NULL;//所以pre的后继应该为NULL 
	}
	 
} 
//在指定位置,插入结点
// pos 指定位置 
void inst(int data,int pos){
	//*pre 前驱结点的指针 
	Node *pre,*np;
	p=head;pre=p;
	//用来记录当前结点是第几个结点 
	int k=0; 
	while(p->next!=NULL){
		p=p->next;
		k++;
		//当前结点到达指定插入结点的位置时		
		if(k==pos){
			np=new Node;
			np->data=data;
			np->next=p;
			pre->next=np;
			break;
		}else{
			pre=p;
		}
	}
	//如果链表中已有的结点不足时 
	if(k<pos){
		np=new Node;
		np->next=NULL;
		np->data=data;
		p->next=np;
	}
}
#include<stdio.h> 
#include<stdlib.h>
 
//单链表  
typedef struct student{ 
	//data是数据域 
	int data; 
	//next是指针域,用于存放单链表指向的下一个链表的地址 
	struct student* next; 
}student;
 
//双向链表  
typedef struct two_student{ 
	//数据域 
	int data; 
	//前驱指针 
	struct two_student* last; 
	//后驱指针 
	struct two_student* next; 
}two_student;

//循环链表: 
void forList( student* p,student* head ) { 
	//如果给的地址是最后一个节点,那么让它指向头节点 
	if( p == NULL ) 
	{ 
		p->next = head; 
	} 
} 

//链表的初始化 
student* start() 
{ 
	//申请一片地址用来存放新开辟的结构体 
	student* tmp = (student*)malloc(sizeof(student)); 
	//刚开辟时,结构体的长度只有1,所以后面不连接结构体,结构体指向空地址 
	tmp->next = NULL; 
	//返回结构体地址 
	return tmp; 
}

//链表动态分配节点,写入数据,传入的data即是你要给结构体内储存的数据。
student* newlist(int data)
{ 
	//动态申请内存,由于tmp是局部变量,所以和start里的tmp不冲突使用
	student* tmp = (student*)malloc(sizeof(student)); 
	//把想要传递的数据通过tmp来赋值 
	tmp->data = data; 
	//确保当前创建的节点是全新节点没有任何链接,方便后续操作 
	tmp->next = NULL; 
	//判断是否创建成功: 
	//malloc如果因为内存不够等原因无法开辟空间,会返回NULL 
	if ( tmp == NULL ) 	{ 
		//给出错误信息 
		printf("开辟空间失败"); 
		//返回空地址 
		return NULL; 
	} 
	else{ 
		//创建成功则返回创建的结构体 
		return tmp; 
	} 
}
 
//链表的遍历输出
void prlist(student* L)
{
	//第一次写这里报错了,大家一定要记住写地址的时候每个变量都需要星号
	student* p, * q;
	//这里的意思是,从L地址开始遍历
	p = L;
	//检测到下一个地址为空即意味着走到了最后一个节点,此时结束
	while ( p->next != NULL )
	{
		q = p->next;
		printf("%d",p->data);
		p = p->next;
	}
}

//头插法创建链表
student* head_newlist(int data[],int n)
{
	//创建头节点
	student* head = (student*)malloc(sizeof(student));
	//给头节点的指针域置空
	head->next = NULL;
	//创建后续节点
	for( int i = 0 ; i < n ; i++ )
	{
		//创建一个节点
		student* node = (student*)malloc(sizeof(student));
		//给节点的数据域赋值
		node->data = data[i];
		//头指针后移
		node->next = head->next; 
		//指针后增 
		head->next = node; 
	} 
	return head; 
}

//尾插法创建链表
student* bottom_newlist(int data[],int n)
{
	//创建头节点
	student* head = (student*)malloc(sizeof(student));
	//创建尾节点
	student* bottom = (student*)malloc(sizeof(student));
	//给头节点的指针域置空
	head->next = NULL;
	//尾指针初始化
	bottom = head;
	//创建后续节点
	for( int i = 0 ; i < n ; i++ )
	{
		//创建一个节点
		student* node = (student*)malloc(sizeof(student));
		//给节点的数据域赋值
		node->data = data[i];		
		//尾指针后移		
		bottom->next = head->next;		
		//尾指针后移		
		bottom->next = node;		
		bottom = node;	
	}	
	//保证最后一个指针指向空	
	bottom->next = NULL;	
	return head;
}

//链表的查找
void findList(student* head,int find)
{
	student* p = head->next;
	while( p != NULL )
	{
		if( p->data == find )
		{
			printf("找到了!");
		}
		p = p->next;
	}
	printf("没找到!");
}
//链表的删除(删除链表中所有的del,如果只想删除第一个del,只需要在185行加入一个return 0; 即可)
int delectList(student* head,int del)
{
	//创建两个指针,分别表示要删除的节点和要删除的节点的前一个节点
	student* p,*q;
	//判断如果是空链表则终止操作
	if( head == NULL || head->next == NULL )
	{
		printf("你的表是空的!");
		return 0;
	}
	//对地址赋值
	p = head;
	q = head->next;
	//遍历链表
	while( p != NULL )
	{
		//如果发现data是要删除的项,则进行删除操作
		if( q->data == del )
		{
		//假定要删除的节点是a,前一项是a-1,后一项是a+1,这里的操作是把a-1和a+1相连
		p->next = q->next;
		//释放a的空间
		free(q);
		printf("删除了一个对应节点");
		}
		else	
		{
			//让指针后移
			p = p->next;
			q = q->next;
		}
	}
	return 0;
}
//链表的插入
int inList(student* head,int data,int count)
{
	//把头节点赋给p方便操作
	student* p = head;
	//获取一个计数器
	int i = 0;
	//寻找到需要插入的点的前一个节点
	while( p->next != NULL && i < count - 1 )
	{
		//指针后移
		p = p->next;
		//循环变量自增
		i++;
	}
	//如果没找到,则返回1表示没有找到可以插入的点(通常是插入节点比链表长度长)
	if( p == NULL )
	{
		return 1;
	}
	else
	{
		//创建一个需要插入的链表
		student* node = (student*)malloc(sizeof(student));
		//给节点的数据域赋值
		node->data = data;
		//新节点的指针域指向链接前的节点指向的后一个节点
		node->next = p->next;
		//新节点链接的前一个节点的指针指向这个新的节点
		p->next = node;
		return 0;
	}
}
//获取链表长度
int getListlenth(student* head)
{
	//获得计数器
	int len = 0;
	//创建指针用于遍历整个链表
	student* p = head->next;
	//迭代遍历获取链表长度
	while( p != NULL )
	{
		len++;
		p = p->next;
	}
	//返回长度
	return len;
}
//链表的完全释放
void clearList(student* head)
{
	student* p;	
	while( head->next != NULL )	
	{	
		p = head;
		head = head->next;	
		free(p);	
	}
} 

int main(){ 
	printf("各种链表的详细用法"); 
	return 0; 
}