1.链表:单链表,双链表,循环链表
2.单链表
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的。
数据元素随机存储,并通过指针表示数据之间逻辑关系的存储结构就是链式存储结构。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
单链表的建立有头插法、尾插法两种方法。
头插法
单链表是用户不断申请存储单元和改变链接关系而得到的一种特殊数据结构,将链表的左边称为链头,右边称为链尾。头插法建单链表是将链表右端看成固定的,链表不断向左延伸而得到的。头插法最先得到的是尾结点。由于链表的长度是随机的,故用一个while循环来控制链表中结点个数。假设每个结点的值都大于O,则循环条件为输入的值大于o。申请存储空间可使用malloc()函数实现,需设立一申请单元指针,但malloc()函数得到的指针并不是指向结构体的指针,需使用强制类型转换,将其转换成结构体型指针。刚开始时,链表还没建立,是一空链表,head指针为NULL。
链表建立的过程是申请空间、得到数据、建立链接的循环处理过程。尾插法
若将链表的左端固定,链表不断向右延伸,这种建立链表的方法称为尾插法。尾插法建立链表时,头指针须设立一个搜索指针,向链表右边延伸,则整个算法中应设立三个链表指针,即头指针head、搜索指针p2、申请单元指针pl。尾插法最先得到的是头结点。
部分内容转载自http://c.biancheng.net/view/3338.html
链表插入元素
虽然新元素的插入位置不固定,但是链表插入元素的思想是固定的,将新元素插入到指定的位置需要进行以下 2 步操作:
(1)将新结点的 next 指针指向插入位置后的结点;
(2)将插入位置前结点的 next 指针指向插入结点;
链表删除元素
从链表中删除指定数据元素时,实则就是将存有该数据元素的节点从链表中摘除,对不再利用的存储空间要及时释放。
因此,从链表中删除数据元素需要进行以下 2 步操作:
(1)将结点从链表中摘下来;
(2)手动释放掉结点,回收被结点占用的存储空间;
link * del = temp->next;//单独设置一个指针指向被删除结点,以防丢失
temp->next = temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域
free(del);//手动释放该结点,防止内存泄漏
链表查找元素
在链表中查找指定数据元素,最常用的方法是:从表头依次遍历表中节点,用被查找元素与各节点数据域中存储的数据元素进行比对,直至比对成功或遍历至链表最末端的 NULL(比对失败的标志)。
链表更新元素
更新链表中的元素,只需通过遍历找到存储此元素的节点,对节点中的数据域做更改操作即可。
3.双链表
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
双向,指的是各节点之间的逻辑关系是双向的,但通常头指针只设置一个,除非实际情况需要。
双向链表添加节点
根据数据添加到双向链表中的位置不同,可细分为以下 3 种情况:
1.添加至表头
将新数据元素添加到表头,只需要将该元素与表头元素建立双层逻辑关系即可。换句话说,假设新元素节点为 temp,表头节点为 head,则需要做以下 2 步操作即可:
- temp->next=head; head->prior=temp;
将 head 移至 temp,重新指向新的表头。例如,将新元素 7 添加至双链表的表头
2.添加至表的中间位置
同单链表添加数据类似,双向链表中间位置添加数据需要经过以下 2 个步骤:
- 新节点先与其直接后继节点建立双层逻辑关系;
新节点的直接前驱节点与之建立双层逻辑关系;
3.添加至表尾
与添加到表头是一个道理,实现过程如下:
- 找到双链表中最后一个节点;
让新节点与最后一个节点进行双层逻辑关系;
line * insertLine(line * head,int data,int add){
//新建数据域为data的结点
line * temp=(line*)malloc(sizeof(line));
temp->data=data;
temp->prior=NULL;
temp->next=NULL;
//插入到链表头,要特殊考虑
if (add==1) {
temp->next=head;
head->prior=temp;
head=temp;
}else{
line * body=head;
//找到要插入位置的前一个结点
for (int i=1; i<add-1; i++) {
body=body->next;
}
//判断条件为真,说明插入位置为链表尾
if (body->next==NULL) {
body->next=temp;
temp->prior=body;
}else{
body->next->prior=temp;
temp->next=body->next;
body->next=temp;
temp->prior=body;
}
}
return head;
}
双向链表删除节点
双链表删除结点时,只需遍历链表找到要删除的结点,然后将该节点从表中摘除即可。例如,删除元素 2 的操作过程如图所示:
//删除结点的函数,data为要删除结点的数据域的值
line * delLine(line * head,int data){
line * temp=head;
//遍历链表
while (temp) {
//判断当前结点中数据域和data是否相等,若相等,摘除该结点
if (temp->data==data) {
temp->prior->next=temp->next;
temp->next->prior=temp->prior;
free(temp);
return head;
}
temp=temp->next;
}
printf("链表中无该数据元素");
return head;
}
双向链表查找节点
通常,双向链表同单链表一样,都仅有一个头指针。因此,双链表查找指定元素的实现同单链表类似,都是从表头依次遍历表中元素。
双向链表更改节点
更改双链表中指定结点数据域的操作是在查找的基础上完成的。实现过程是:通过遍历找到存储有该数据元素的结点,直接更改其数据域即可。
4.循环链表
无论是静态链表还是动态链表,有时在解决具体问题时,需要我们对其结构进行稍微地调整。比如,可以把链表的两头连接,使其成为了一个环状链表,通常称为循环链表。只需要将表中最后一个结点的指针指向头结点,链表就能成环
需要注意的是,虽然循环链表成环状,但本质上还是链表,因此在循环链表中,依然能够找到头指针和首元节点等。所以,不要随意改变头指针的指向。在使用循环链表时,附带最多的操作就是遍历链表。
5.静态链表
静态链表,也是线性存储结构的一种,它兼顾了顺序表和链表的优点于一身,可以看做是顺序表和链表的升级版。使用静态链表存储数据,数据全部存储在数组中(和顺序表一样),但存储位置是随机的,数据之间"一对一"的逻辑关系通过一个整形变量(称为"游标",和指针功能类似)维持(和链表类似)。
通过 "数组+游标" 的方式存储具有线性关系数据的存储结构就是静态链表。
静态链表会将第一个数据元素放到数组下标为 1 的位置(a[1])中。从 a[1] 存储的数据元素 1 开始,通过存储的游标变量 3,就可以在 a[3] 中找到元素 1 的直接后继元素 2;同样,通过元素 a[3] 存储的游标变量 5,可以在 a[5] 中找到元素 2 的直接后继元素 3,这样的循环过程直到某元素的游标变量为 0 截止(因为 a[0] 默认不存储数据元素)。