链表的定义
- n个节点离散分配 彼此通过指针相连
- 每个节点只有一个前驱节点 每个节点只有一个后驱节点(单链表)
- 首节点没有前驱节点 尾节点没有后驱节点
- 注意了解首节点,尾节点,头结点,头指针,尾指针各个术语的含义
头指针:
- 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
- 头指针具有标识作用,所以常用头指针冠以链表的名字
- 无论链表是否为空,头指针均不为空,头指针是链表的必要元素
头结点:
- 头结点是为了操作的统一和方便设立的,放在第一元素的结点之前,其数据域一般无意义,也可存放链表的长度
- 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了
- 头结不一定是链表的必要元素
单链表的C语言代码
- 由于单链表的代码不难而且十分易懂,我将直接给出代码
- 下面代码包括对链表的一些基本操作,包括但不限于增删改查,函数后会有标注
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
typedef struct Node{
int data;//数据域
struct Node * pNext;//指针域
}NODE,*PNODE;
PNODE create_list(void);//创建链表
void traverse_list(PNODE pHead);//遍历链表
bool is_empty(PNODE pHead);//判断链表是否为空
int length_list(PNODE pHead);//链表的长度
bool insert_list(PNODE,int,int);//插入元素
bool delete_list(PNODE,int,int *);//删除元素
void sort_list(PNODE);//排序
bool change_list(PNODE,int,int);//改变元素
bool find_list(PNODE,int,int *);//查找元素
int main(void)
{
PNODE pHead=NULL;
int val,pos;
pHead=create_list();//创建一个非循环单链表,并将该链表的头结点赋给pHead
traverse_list(pHead);
if(is_empty(pHead))
printf("链表为空\n");
else
printf("链表非空\n");
int len=length_list(pHead);
printf("链表的长度为:%d\n",len);
sort_list(pHead);
printf("排序后的链表为:");
traverse_list(pHead);
printf("请输入要插入的位置和插入的数值:");
scanf("%d %d",&pos,&val);
insert_list(pHead,pos,val);
printf("插入后的链表为:");
traverse_list(pHead);
printf("请输入要删除的位置:");
scanf("%d",&pos);
if(delete_list(pHead,pos,&val)){
printf("删除成功!\n");
printf("删除的数为:%d\n",val);
printf("删除后的链表为:");
traverse_list(pHead);
}
else
printf("删除失败!\n");
printf("请输入要修改的位置和修改的数值:");
scanf("%d %d",&pos,&val);
if(change_list(pHead,pos,val)){
printf("修改成功!\n");
printf("修改后的链表为:");
traverse_list(pHead);
}
else
printf("修改失败!\n");
printf("请输入要查找的位置:");
scanf("%d",&pos);
if(find_list(pHead,pos,&val)){
printf("查找成功!\n");
printf("当前位置的的数为:%d\n",val);
}
else
printf("查找失败!\n");
return 0;
}
PNODE create_list(void){
int len;
int i;
int val;
PNODE pHead=(PNODE)malloc(sizeof(NODE));
PNODE pTail=pHead;
pTail->pNext=NULL;
if(NULL==pHead){
printf("分配失败!\n");
exit(-1);
}
printf("请输入要生成的节点个数:");
scanf("%d",&len);
for(i=0;i<len;i++){
printf("请输入第%d个节点的值:",i+1);
scanf("%d",&val);
PNODE pNew=(PNODE)malloc(sizeof(NODE));
if(NULL==pNew){
printf("分配失败!\n");
exit(-1);
}
pNew->data=val;
pTail->pNext=pNew;
pNew->pNext=NULL;
pTail=pNew;
}
return pHead;
}
void traverse_list(PNODE pHead){
PNODE p=pHead->pNext;
while(NULL!=p){
printf("%d ",p->data);
p=p->pNext;
}
printf("\n");
return;
}
bool is_empty(PNODE pHead){
if(pHead->pNext==NULL)
return true;
return false;
}
int length_list(PNODE pHead){
PNODE p=pHead->pNext;
int len=0;
while(p!=NULL){
len++;
p=p->pNext;
}
return len;
}
void sort_list(PNODE pHead){
int i,j,t;
int len=length_list(pHead);
PNODE p,q;
for(i=0,p=pHead->pNext;i<len-1;i++,p=p->pNext){
for(j=i+1,q=p->pNext;j<len;j++,q=q->pNext){
if(p->data>q->data){
t=p->data;
p->data=q->data;
q->data=t;
}
}
}
return;
}
//在pHead所指向的第pos个节点的前面插入一个新的节点,该节点的值为val并且pos的值从1开始
bool insert_list(PNODE pHead,int pos,int val){
int i=0;
PNODE p=pHead;
while(NULL!=p&&i<pos-1){
p=p->pNext;
i++;
}
if(i>pos-1||NULL==p)
return false;
PNODE pNew=(PNODE)malloc(sizeof(NODE));
if(pNew==NULL){
printf("\n");
exit(-1);
}
pNew->data=val;
pNew->pNext=p->pNext;
p->pNext=pNew;
return true;
}
bool delete_list(PNODE pHead,int pos,int *pVal){
int i=0;
PNODE p=pHead;
while(NULL!=p->pNext&&i<pos-1){
p=p->pNext;
i++;
}
if(i>pos-1||NULL==p->pNext)
return false;
PNODE q=p->pNext;
*pVal=q->data;
p->pNext=q->pNext;
free(q);
q=NULL;
return true;
}
bool change_list(PNODE pHead,int pos,int val){
int i=0;
PNODE p=pHead;
while(NULL!=p&&i<pos){
p=p->pNext;
i++;
}
if(i>pos||NULL==p)
return false;
p->data=val;
return true;
}
bool find_list(PNODE pHead,int pos,int *pVal){
int i=0;
PNODE p=pHead;
while(NULL!=p&&i<pos){
p=p->pNext;
i++;
}
if(i>pos||NULL==p)
return false;
*pVal=p->data;
return true;
}
循环链表
- 将单链表中终端结点的指针域由空改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表
- 循环链表带有头结点的空链表与非空的循环链表如下图:
- 但是我们如果想要访问最后一个结点,时间复杂度就和不循环的单链表一样了,为了缩短访问时间,我们引入了访问终端结点的尾指针如rear,这样我们的开始结点就是rear->next->next
如何将两个循环链表合并成一个表
- 上面的两个循环单链表,它们的尾指针分别是rearA和rearB,至于如何合并,如下:
p=rearA->next; //保存A的头结点,即①
rearA->next=rear->B->next->next; //将B的第一个结点赋给rearA->next,即②
rearB->next=p; //将原A的头结点赋给rearB->next,即③
free( p ); //释放p
- 循环链表的操作和单链表很向,适当改变代码即可
双向链表
- 定义双向链表的结构体如下
typedef struct DulNode{
int data;
struct DulNode *prior;
struct DulNode *next;
}DulNode,*PDulNode;
- 双向链表的循环带头结点的空链表与非空带头结点的双向链表如下图:
- 由于这是双向链表,那么对于链表中的某一个结点p,它的后继的前驱是自己,前驱的后继也是自己
p->next->prior=p=p->prior->next
- 由于双向链表是由单链表拓展出来的结构,所以很多操作是与单链表相同的
插入元素
- 这个操作并不难,但是我们要注意更改的顺序
- 现在我们假设储存元素e的结点为s,要实现将结点s插入到结点p和p->next之间需要的步骤如下
s->prior=p;//把p赋给s的前驱,如图中①
s->next=p->next;//把p->next赋给s的后驱,如图中②
p->next->prior=s;//把s赋给p->next的前驱,如图中③
p->next=s;//把s赋值给p的后驱,如图中④
删除元素
- 若删除结点p,如图:
p->prior->next=p->next;//把p->next赋值给p->prior的后继,如图中①
p->next->prior=p->prior;//把p->prior赋值给p->next的前驱,如图中②
free( p );//释放结点