我是荔园微风,作为一名在IT界整整25年的老兵,今天总结一下Visual C++环境下调试数据结构——线性表之链式存储结构。
我为什么要写这篇贴子呢?类似的贴子已经很多了,为什么还要再写这个话题。
不知道大家有没有发现一个问题,就是很多介绍链式存储结构的贴子里的代码一写入到VC里根本不能运行的,不是这个符号错,就是那个指针错。
所以我一定要避免这个问题,写完本文后我检查了很多遍,力保所有的C语言代码的每一个字母、每一个指针、每一个符号都不能出错。以保证大家得到真正的可运行代码。
好了,让我们开始吧。
线性表的链式存储是用通过指针链接起来的结点来存储数据元素,基本的结点结构如下所示:
其中,数据域用于存储数据元素的值,指针域则存储当前元素的直接前驱或直接后继的位置信息,指针域中的信息称为指针。
存储各数据元素的结点的地址并不要求是连续的,因此存储数据元素的同时必须存储元素之间的逻辑关系。另外,结点空间只有在需要的时候才申请,无须事先分配。结点之间通过指针域构成一个链表,若结点中只有一个指针域,则称为线性链表或单链表,如图所示:
设线性表中的元素是整型,则单链表结点类型的定义为:
typedef struct node{
int data; /*结点的数据域,此处以整型为例*/
struct node *next; /*结点的指针域*/
}NODE,*LinkList;
在链式存储结构中,只需要一个指针(称为头指针,如上图中的head)指向第一个结点,就可以顺序地访问到表中的任意一个元素。
在链式存储结构下进行插入和删除,其实质都是对相关指针的修改。在单链表中,若在p所指结点后插入新元素结点(s所指结点,已经生成),如下图所示,其基本步骤如下。
(1) s->next=p->next;
(2) p->next=s;
即先将p所指结点的后继结点指针赋给s所指结点的指针域,然后将p所指结点的指针域修改为指向s所指结点。
同理,在单链表中删除p所指结点的后继结点时(如上图所示),步骤如下。
(1) q=p->next;
(2) p->next=p->next->next;
(3) free(q);
即先令临时指针q指向待删除的结点,然后修改p所指结点的指针域为指向p所指结点的后继的后继结点,从而将元素b所在的结点从链表中删除,最后释放q所指结点的空间。
在实际应用中,为了简化对链表状态的判定和处理,特别引入一个不存储数据元素的结点,称为头结点,将其作为链表的第一个结点并令头指针指向该结点。
下面给出单链表上查找、插入和删除运算的实现过程。
【函数】单链表的查找运算。
LinkList Find_List(LinkList L, int k) /*L为带头结点单链表的头指针*/
/*在表中查找第k个元素,若找到,返回该元素结点的指针;否则,返回空指针NULL*/
{ LinkList p; int i;
i=1; p=L->next; /*初始时,令p指向第一个元素结点,i为计数器*/
while(p && i<k){ /*顺指针链向后查找直到p指向第k个元素结点或p为空指针*/
p=p->next; i++;
}
if(p && i==k) return p; /*存在第k个元素且指针p指向该元素结点*/
return NULL; /*第k个元素不存在,返回空指针*/
}/*Find_List*/
【函数】单链表的插入运算。
int Insert_List(LinkList L, int k, int newElem) /*L为带头结点单链表的头指针*/
/*将元素newElem插入表中的第k个元素之前,若成功则返回0,否则返回-1*/
/*该插入操作等同于将元素newElem插入在第k-1个元素之后*/
{ LinkList p,s; /*p、s为临时指针*/
if(k==1) p=L; /*元素newElem要插入到第1个元素之前*/
else p=Find_List(L, k-1); /*查找表中的第k-1个元素并令p指向该元素结点*/
if(!p) return -1; /*表中不存在第k-1个元素,不满足运算要求*/
s=(NODE*)malloc(sizeof(NODE)); /*创建新元素的结点空间*/
if (!s) return -1;
s->data=newElem;
s->next=p->next; p->next=s; /*将元素newElem插入第k-1个元素之后*/
retun 0;
)/*Insert List*/
【函数】单链表的刑除运算。
int Delete_List(LinkList L, int k) /*L为带头结点单链表的头指针*/
/*删除表中的第k个元素结点,若成功则返回0,否则返回-1*/
/*删除第k个元素相当于令第k-1个元素结点的指针域指向第k+1个元素所在结点*/
{ LinkList p,q; /*p、g为临时指针*/
if (k==1) p=L; /*删除的是第一个元素结点*/
else p=Find_List(L, k-1); /*查找表中的第k-1个元素并令p指向该元素结点*/
if (!p ll !p->next) return -1; /*表中不存在第k个元素*/
q=p->next; /*令q指向第k个元素结点*/
p->next=q->next; free(q); /*删除结点*/
return 0;
}/*Delete_List */
当线性表采用链表作为存储结构时,不能对数据元素进行随机访问,但是具有插入和删除操作不需要移动元素的优点。
根据结点中指针域的设置方式,还有其他几种链表结构。
双向链表。每个结点包含两个指针,分别指出当前元素的直接前驱和直接后继。其特点是可以从表中任意的结点出发,从两个方向上遍历链表。
循环链表。在单向链表(或双向链表)的基础上令表尾结点的指针指向链表的第一个结点,构成循环链表。其特点是可以从表中任意结点开始遍历整个链表。
静态链表。借助数组来描述线性表的链式存储结构,用数组元素的下标表示元素所在结点的指针。
若双向链表中结点的front和next指针域分别指示当前结点的直接前驱和直接后继,则在双向链表中插入结点*s时指针的变化情况如图所示,其操作过程可表示为:
(1) s->front =p->front;
(2) p->front->next=s; //或者表示为s->front->next=s;
(3) s->next=p;
(4) p->front=s;
在双向链表中删除结点时指针的变化情况如图所示,其操作过程可表示为:
(1) p->front->next=p->next;
(2) p->next->front =p->front;
(3) free(p);
大家可以把以上代码代入程序进行调试。相信过程会非常顺利。每一个字母、每一个指针、每一个符号我都检查过了。请大家放心使用。
作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。