C语言版数据结构(从0开始)2.链表(建立链表,有图有代码)

链表

1.链表的概念

用一组任意的存储单元(可以是无序的)存放线性表的数据元素。
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点组成,结点可以在运行时动态生成。

2.链表的特点

线性表的链式存储表示的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。

3.结点的组成

每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。

数据域—>表示数据元素自身值
指针域(链域)—>表示与其它结点关系
通过链域,可将n个结点按其逻辑顺序链接在一起(不论其物理次序如何)。

单链表

1.单链表概念

1.开始结点(无前趋) —用头指针指向之。
2.最后一个结点(尾结点)—指针为空(无后继),用 ^或null表示。
3.表中其它结点—由其前趋的指针域指向之。
4.空表—头指针为空。
5.头结点—其指针域 指向表中第一个结点的指针。

2.单链表的描述

单链表由头指针唯一确定,因此单链表可以用头指针的名字来命名。
例如:若头指针为head,则可把链表称为“表head”。

head
a1
a2
...
an
null

若Mermaid流程图不够直观,可继续阅读本文后续的图片。

3.C语言结构定义

typedef  char datatype;
typedef struct node     /*结点类型定义*/
{datatype data;         /*数据域*/
struct node *next;
}ListNode;              /*next为指针域, 指向该结点的后继*/
ListNode *head,*p;      /*指针类型说明*/

指针p与指向的结点关系示意图:

说明:
(由于markdown编辑器格式问题,说明写在代码块部分)

*p---指向链表中某一结点的指针。
*p---表示 由指针p所指向的结点。
(*p).data或p->data---表示由p所指向结点的数据域。
(*p).next或p->next---表示由p所指向结点的指针域。
*P=(ListNode*)malloc(sizeof(ListNode))---对指针p赋值使其指向某一结点
                             (按需生成一个ListNode结点类型的新结点)。
其中: 
(ListNode*)----进行类型转换。
Sizeof(ListNode)---求结点需用占用的字节数。
Malloc(size)--在内存中分配size个连续可用字节的空间。
Free(p)--系统回收p结点。(动态)

4.单链表的基本运算

建立单链表

1.头插法建表:

从一个空表开始,重复读入数据,生成新结点,将读入数据存放在新结点的数据域 中,然后将新结点插入到当前链表的表头上,直到读入结束标志为止。
也就是说,输入的第一个结点在最终的链表中为最后一个结点。头插法生成的链表中结点的入的顺序相反次序和输。

qq-735429101
qq-735429101
步骤1建立新的结点,数据域输入D。步骤2新结点D的指针域指向头指针(即指向结点C)。步骤3和4为同一操作,图中步骤3表达不够好,令头指针指向新的结点D,头指针就不再指向结点C,就同时完成步骤3和4。如此反复即可。

头插法建表C语言实现:

typedef  char datatype;

typedef struct node                /*结点类型定义*/
{datatype data;                    /*数据域*/
   struct node *next; }ListNode;   /*next为指针域,指向该结点的后继*/
 ListNode *head,*p;                /*指针类型说明*/
 
ListNode *createlistf()
{char ch;                          /*逐个插入字符,以“$“为结束符,返回单链表头指针*/
  ListNode *head,*s;
  head=NULL;                      /*链表开始为空*/
  ch=getchar();                   /*读入第一个结点的值*/
  while(ch!='$')
  {s=(ListNode *)malloc(sizeof(ListNode));  /*生成新结点,注意malloc函数的头文件*/
    s->data=ch;
  s->next=head;
    head=s;
    ch=getchar();
 }
 return head;
 }
2.尾插法建表:

将新结点插入到当前链表的表尾上,可增加一个尾指针r,使其始终指向链表的尾结点。
和头插法不同,尾插建表可使生成的结点次序和输入的顺序相同。
qq-735429101
qq-735429101
步骤1建立新的结点,数据域输入D。步骤2利用尾指针r使结点C的指针域指向新结点D。步骤3令尾指针r指向新结点D,不再指向结点C。如此反复即可。

头插法建表C语言实现:

typedef  char datatype;

typedef struct node                /*结点类型定义*/
{datatype data;                    /*数据域*/
   struct node *next; }ListNode;   /*next为指针域, 指向该结点的后继*/
 ListNode *head,*p;                /*指针类型说明*/
 
ListNode *creatlistr()
{
	char ch;                      /*逐个插入字符,以“$“为结束符,返回单链表头指针*/
    ListNode *head,*s,*r;
    head=NULL;                    /*链表开始为空*/
    r=NULL;                       /*尾指针初值为空*/
    ch=getchar();                 /*读入第一个结点的值*/
    while(ch!='$')                /*“$“为输入结束符*/ 
	{s=(ListNode *)malloc(sizeof(ListNode));  /*生成新结点*/
     s->data=ch;
     if(head==NULL)head=s;
	 else r->next=s;
	 r=s;
     ch=getchar();
}
	if(r!=NULL) r->next=NULL;
    return head;
} 
3.尾插法建表的改进算法

头、尾插法建表分析:
上述头、尾插法建表由于没有生成(附加)头结点,因此开始结点和其它结点的插入处理并不一样,原因是开始结点的位置存放在头指针中,而其余结点的位置是在其前趋结点的指针域 。

改进思想:
设头结点,使第一个结点和其余结点的插入操作一致。
头结点(在第一个结点之前附设)—其指针域存贮指向第一个结点的指针(即第一个结点的存贮位置)。
头结点的数据域:可有可无
头结点的指针域:指向第一个结点的指针。

带头结点的单链表示意图:
qq-73542901
无论链表是否为空,其头指针是指向头结点的非空指针,所以表的第一个结点和其它结点的操作一致。

C语言实现:

ListNode *CREATLISTR1()             /*带头结点的尾插法建立单链表,返回表头指针*/
{ char ch; 
  ListNode *head,*s,*r;
  head=malloc(sizeof(ListNode));    /*生成头结点*/
  r=head;                           /*尾指针初值指向头结点*/
  ch=getchar(); 
  while(ch!='$')                    /*“$“为输入结束符*/
  {s=(ListNode *)malloc(sizeof(ListNode));/*生成新结点*/
  s->data=ch;
  r->next=s;                        /*新结点插入表尾*/
  r=s;                              /*尾指针r指向新的表尾*/
  ch=getchar();                     /*读下一结点*/
  }
  r->next=NULL;
  return head;
}

查找运算

1.按序号查找

设单链表的长度为n,要查找表中第I个结点。
算法思想:
从头结点开始顺链扫描,用指针p指向当前扫描到的结点,用 j 作统计已扫描结点数的计数器,当 p 扫描下一个结点时,j 自动加1。p 的初值指向头结点,j 的初值为0。当 j=i 时,指针p 所指的结点就是第 i 个结点。

算法描述:

ListNode *GET(head,i)
ListNode *head;
int i;
{ int j;
  ListNode *p;
  p=head;j=0;
  while((p->next!=null&&(j<i))
 {p=p->next;j++;}
  if (i=j) return p; /*找到第i个结点*/
  else return null;  /*找不到,则返回null*/
}
2.按值查找:

在链表中,查找是否有结点等于给定值key 的结点,若有的话,则返回首次找到的值为key的结点的存储位置;否则返回null。
算法思想:
从开始结点出发,顺链逐个结点的值和给定值key 作比较。

插入运算

设指针p 指向单链表的某一结点,指针s 指向等待插入的、其值为 x 的新结点。
实现方法(两种):
①后插–将新结点s 插入结点p 之后。
②前插–将新结点s 插入结点p 之前。

1.后插操作

取一新结点,将其数据域置为新结点,再修改有关结点的链域。
把原 p结点的直接后继结点作为s结点的直接后继,s结点作为 p结点的直接后继。
qq-735429101
C语言实现:

struct ListNode *p,*s;
datatype x;
char INSERTAFTER(char *P,char X) /*将值为X的新结点插入*P之后*/
{ 
	s=(ListNode *)malloc(sizeof(ListNode));  /*生成新结点*/
    s->data=x;
    s->next=p->next;p->next=s;    /*将*s插入*p之后*/
} 
2.前插操作

先找到 p结点的前趋结点(单链表无前趋指针),然后修改其链域,使其指向待插入的 s结点,而将 s结点指向 p结点。
qq735429101
C语言实现:

struct ListNode *head,*p;
datatype x;
char INSERTBEFORE(char *head,char *p,char x)/*在带头结点的单链表head中,将值为X的新结点插入*P之前*/
{
	ListNode *q,*s;
	s=(ListNode *)malloc(sizeof(ListNode));/*生成新结点*/
	s->data=x;
	q=head;                /*从头指针开始*/
	while(q->next!=p)
		q=q->next;         /*找*p的前趋结点*/
	s->next=p;q->next=s;  /*将*s插入*p之 前*/
} 
3.改进的前插算法

后插算法为O(1),而前插算法为O(n),可在结点p 之后先插入新结点s 然后交换结点s 和结点p 的值。
qq-735429101

删除运算

先找到被删结点(第 i 个)的前趋结点,即第 i-1 个结点p ,然后删除 p的后继。
qq-735429101算法描述:

Delete(p)  /*删除*p的后继结点*r,设*r存在*/
ListNode *p;
{
	ListNode *r;
	if(p->next!=null)
		r=p->next;
	p->next=r->next;        
	free(r);
}

循环链表

1.循环链表的概念

循环链表是另一种形式的链式存贮结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
(1)单循环链表——在单链表中,将终端结点的指针域NULL改为指向表头结点或开始结点即可。
(2)多重链的循环链表——将表中结点链在多个环上。

2.循环链表特点

循环链表的特点是无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活。
用尾指针rear表示的单循环链表对开始结点a1和终端结点an查找时间都是O(1)。而表的操作常常是在表的首尾位置上进行,因此,实用中多采用尾指针表示单循环链表。
判断空链表的条件与单链表不同:

head==head->next;
rear==rear->next;

3.C语言实现

【例】在链表上实现将两个线性表(a1,a2,…,an)和(b1,b2,…,bm)连接成一个线性表(a1,…,an,b1,…bm)的运算。
分析:若在单链表或头指针表示的单循环表上做这种链接操作,都需要遍历第一个链表,找到结点an,然后将结点b1链到an的后面,其执行时间是O(n)。若在尾指针表示的单循环链表上实现,则只需修改指针,无须遍历,其执行时间是O(1)。

LinkListConnect(LinkListA,LinkListB)
{//假设A,B为非空循环链表的尾指针
LinkListp=A->next;//保存A表的头结点位置
A->next=B->next->next;//B表的开始结点链接到A表尾
free(B->next);//释放B表的头结点
B->next=p;
returnB;//返回新循环链表的尾指针
}

4.基本运算(12种)

建立单循环链表

void InitList(LinkList *L) {
/* 操作结果:构造一个空的线性表L */
*L=(LinkList)malloc(sizeof(struct LNode));
/* 产生头结点,并使L指向此头结点 */
if(!*L) /* 存储分配失败 */
exit(OVERFLOW);
(*L)->next=*L;
/* 指针域指向头结点 */
}

销毁链表

void DestroyList(LinkList *L) {
/* 操作结果:销毁线性表L */
LinkList q,p=(*L)->next;
/* p指向头结点 */
while(p!=*L) { /* 没到表尾 */
q=p->next;
free(p);
p=q;
}
free(*L);
*L=NULL;
}

重置为空表

void ClearList(LinkList *L)
/* 改变L */
{
/* 初始条件:线性表L已存在。操作结果:将L重置为空表 */
LinkList p,q;
*L=(*L)->next;
/* L指向头结点 */
p=(*L)->next;
/* p指向第一个结点 */
while(p!=*L)
/* 没到表尾 */
{
q=p->next;
free(p);
p=q;
}
(*L)->next=*L;
/* 头结点指针域指向自身 */
}

验证是否为空表

Status ListEmpty(LinkList L) {
/* 初始条件:线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */
if(L->next==L)
/* 空 */
return TRUE;
else
return FALSE;
}

计算表内元素个数

int ListLength(LinkList L) {
/* 初始条件:L已存在。操作结果:返回L中数据元素个数 */
int i=0;
LinkList p=L->next;
/* p指向头结点 */
while(p!=L)
/* 没到表尾 */
{
i++;
p=p->next;
}
return i;
}

赋值

Status GetElem(LinkList L,int i,ElemType *e) {
/* 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR */
int j=1;
/* 初始化,j为计数器 */
LinkList p=L->next->next;
/* p指向第一个结点 */
if(i<=0||i>ListLength(L))
/* 第i个元素不存在 */
return ERROR;
while(j< i) {
/* 顺指针向后查找,直到p指向第i个元素 */
p=p->next;
j++;
}
*e=p->data; /* 取第i个元素 */ return OK;
}

按值查找元素

int LocateElem(LinkList L,ElemType e,Status(*compare)(ElemType,ElemType)) {
/* 初始条件:线性表L已存在,compare()是数据元素判定函数 */ /* 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。*/ /* 若这样的数据元素不存在,则返回值为0 */ int i=0;
LinkList p=L->next->next; /* p指向第一个结点 */ while(p!=L->next) {
i++;
if(compare(p->data,e)) /* 满足关系 */
return i;
p=p->next;
}
return 0;
}

查找元素前驱

Status PriorElem(LinkList L,ElemType cur_e,ElemType *pre_e) {
/* 初始条件:线性表L已存在 */ /* 操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,*/ /* 否则操作失败,pre_e无定义 */ LinkList q,p=L->next->next; /* p指向第一个结点 */ q=p->next;
while(q!=L->next) { /* p没到表尾 */
if(q->data==cur_e) {
*pre_e=p->data;
return TRUE;
}
p=q;
q=q->next;
}
return FALSE; /* 操作失败 */
}

查找元素后继

Status NextElem(LinkList L,ElemType cur_e,ElemType *next_e) {
/* 初始条件:线性表L已存在 */ /* 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,*/ /* 否则操作失败,next_e无定义 */ LinkList p=L->next->next; /* p指向第一个结点 */ while(p!=L) { /* p没到表尾 */
if(p->data==cur_e) {
*next_e=p->next->data;
return TRUE;
}
p=p->next;
}
return FALSE; /* 操作失败 */
}

插入元素

Status ListInsert(LinkList *L,int i,ElemType e) { /* 改变L */
/* 在L的第i个位置之前插入元素e */ LinkList p=(*L)->next,s; /* p指向头结点 */ int j=0;
if(i<=0||i>ListLength(*L)+1) /* 无法在第i个元素之前插入 */
return ERROR;
while(j< i-1) { /* 寻找第i-1个结点 */
p=p->next;
j++;
}
s=(LinkList)malloc(sizeof(struct LNode)); /* 生成新结点 */ s->data=e; /* 插入L中 */ s->next=p->next;
p->next=s;
if(p==*L) /* 改变尾结点 */
*L=s;
return OK;
}

删除元素

Status ListDelete(LinkList *L,int i,ElemType *e) { /* 改变L */
/* 删除L的第i个元素,并由e返回其值 */ LinkList p=(*L)->next,q; /* p指向头结点 */ int j=0;
if(i<=0||i>ListLength(*L)) /* 第i个元素不存在 */
return ERROR;
while(j< i-1) { /* 寻找第i-1个结点 */
p=p->next;
j++;
}
q=p->next; /* q指向待删除结点 */ p->next=q->next;
*e=q->data;
if(*L==q) /* 删除的是表尾元素 */
*L=p;
free(q); /* 释放待删除结点 */ return OK;
}

调用函数

void ListTraverse(LinkList L,void(*vi)(ElemType)) {
/* 初始条件:L已存在。操作结果:依次对L的每个数据元素调用函数vi() */ LinkList p=L->next->next; /* p指向首元结点 */ while(p!=L->next) { /* p不指向头结点 */
vi(p->data);
p=p->next;
}
printf("\n");
}

双向链表

1.双向链表基本概念

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

2.线性表的双向链表存储结构

typedef struct DuLNode
{
ElemType data;
struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;

3.双链表特点

双链表一般也由头指针head唯一确定。
每一结点均有:
数据域(data)
左链域 (prior)指向前趋结点.
右链域 (next)指向后继。
是一种对称结构(既有前趋,又有后继)。
双链表每个数据元素具有两个指针域,分别指向该数据元素的前驱和后继

4.基本运算

带头结点的双向循环链表的基本操作

void InitList(DuLinkList L)
{ /* 产生空的双向循环链表L */
L=(DuLinkList)malloc(sizeof(DuLNode));
if(L)
L->next=L->prior=L;
else
exit(OVERFLOW);
}

销毁双向循环链表

void DestroyList(DuLinkList L)
{
DuLinkList q,p=L->next; /* p指向第一个结点 */
while(p!=L) /* p没到表头 */
{
q=p->next;
free(p);
p=q;
}
free(L);
L=NULL;
}

重置链表为空表

void ClearList(DuLinkList L) /* 不改变L */
{  DuLinkList q,p=L->next; /* p指向第一个结点 */
while(p!=L) /* p没到表头 */
{
q=p->next;
free(p);
p=q;
}
L->next=L->prior=L; /*头结点的两个指针域均指向自身 */
}

验证是否为空表

Status ListEmpty(DuLinkList L)
{ /* 初始条件:线性表L已存在
if(L->next==L&&L->prior==L)
return TRUE;
else
return FALSE;
}

计算表内元素个数

int ListLength(DuLinkList L)
{ /* 初始条件:L已存在。操作结果: */
int i=0;
DuLinkList p=L->next; /* p指向第一个结点 */
while(p!=L) /* p没到表头 */
{
i++;
p=p->next;
}
return i;
}

赋值

Status GetElem(DuLinkList L,int i,ElemType *e)
{ /* 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR */
int j=1; /* j为计数器 */
DuLinkList p=L->next; /* p指向第一个结点 */
while(p!=L&&j<i)
{
p=p->next;
j++;
}
if(p==L||j>i) /* 第i个元素不存在 */
return ERROR;
*e=p->data; /* 取第i个元素 */
return OK;
}

查找元素

int LocateElem(DuLinkList L,ElemType e,Status(*compare)(ElemType,ElemType))
{ /* 初始条件:L已存在,compare()是数据元素判定函数 */
/* 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int i=0;
DuLinkList p=L->next; /* p指向第1个元素 */
while(p!=L)
{
i++;
if(compare(p->data,e)) /* 找到这样的数据元素*/
return i;
p=p->next;
}
return 0;
}

查找元素前驱

Status PriorElem(DuLinkList L,ElemType cur_e,ElemType *pre_e)
{ /* 操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱, */
/* 否则操作失败,pre_e无定义 */
DuLinkList p=L->next->next; /* p指向第2个元素 */
while(p!=L) /* p没到表头 */
{
if(p->data==cur_e)
{
*pre_e=p->prior->data;
return TRUE;
}
p=p->next;
}
return FALSE;
}

查找元素后继

Status NextElem(DuLinkList L,ElemType cur_e,ElemType *next_e)
{ /* 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继, */
/* 否则操作失败,next_e无定义 */
DuLinkList p=L->next->next; /* p指向第2个元素 */
while(p!=L) /* p没到表头 */
{
if(p->prior->data==cur_e)
{
*next_e=p->data;
return TRUE;
}
p=p->next;
}
return FALSE;
}

查找元素地址

DuLinkList GetElemP(DuLinkList L,int i) /* 另加 */
{ /* 在双向链表L中返回第i个元素的地址。i为0,返回头结点的地址。若第i个元素不存在,*/
/* 返回NULL */
int j;
DuLinkList p=L; /* p指向头结点 */
if(i<0||i>ListLength(L)) /* i值不合法 */
return NULL;
for(j=1;j<=i;j++)
p=p->next;
return p;
}

插入元素

Status ListInsert(DuLinkList L,int i,ElemType e)
{ /* 在带头结点的双链循环线性表L中第i个位置之前插入元素e,i的合法值为1≤i≤表长+1 */
/* 改进算法2.18,否则无法在第表长+1个结点之前插入元素 */
DuLinkList p,s;
if(i<1||i>ListLength(L)+1) /* i值不合法 */
return ERROR;
p=GetElemP(L,i-1); /* 在L中确定第i个元素前驱的位置指针p */
if(!p) /* p=NULL,即第i个元素的前驱不存在(设头结点为第1个元素的前驱) */
return ERROR;
s=(DuLinkList)malloc(sizeof(DuLNode));
if(!s)
return OVERFLOW;
s->data=e;
s->prior=p; /* 在第i-1个元素之后插入 */
s->next=p->next;
p->next->prior=s;
p->next=s;
return OK;
}

删除元素

Status ListDelete(DuLinkList L,int i,ElemType *e)
{ /* 删除带头结点的双链循环线性表L的第i个元素,i的合法值为1≤i≤表长 */
DuLinkList p;
if(i<1) /* i值不合法 */
return ERROR;
p=GetElemP(L,i); /* 在L中确定第i个元素的位置指针p */
if(!p) /* p=NULL,即第i个元素不存在 */
return ERROR;
*e=p->data;
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);
return OK;
}

正序查找

void ListTraverse(DuLinkList L,void(*visit)(ElemType))
{ /* 由双链循环线性表L的头结点出发,正序对每个数据元素调用函数visit() */
DuLinkList p=L->next; /* p指向头结点 */
while(p!=L)
{
visit(p->data);
p=p->next;
}
printf("\n");
}
void ListTraverseBack(DuLinkList L,void(*visit)(ElemType))

逆序查找

{ /* 由双链循环线性表L的头结点出发,逆序对每个数据元素调用函数visit()。另加 */
DuLinkList p=L->prior; /* p指向尾结点 */
while(p!=L)
{
visit(p->data);
p=p->prior;
}
printf("\n");
}

本文部分参照百度百科相关内容。

.
.
.

文章内容来源于博主老师传授、自身理解以及网络收集

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值