一、概述
-
链式存储结构是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。又称链表。
-
链表特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些数据元素可以存在内存未被占用的任意位置(如图所示)。
-
以前在顺序结构中,每个数据元素只需要存数据元素信息就可以了。现在链式结构中,除了要存数据元素信息外,还要存储它的后继元素的存储地址。
-
把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域,指针域中存储的信息称做指针或链;这两部分信息组成 数据元素的存储映像,称为结点(node)。
-
链表中第一个结点的存储位置叫做头指针。
-
在单链表中的第一个结点前附设一个结点,叫头结点。
-
头结点不是链表的必要元素
-
链表存储如下图所示
二、单向链表
- C结构体
typedef int datatype; //元素数据域类型可为任一类型,不局限于int
struct node
{
datatype data;
struct node *next;
};
2.1 有头结点的实现方式
(1)创建链表,生成一个头结点
struct node *list_create(void)
{
struct node *list;
list = (struct node *)malloc(sizeof(struct node));
if (list == NULL)
return NULL;
list->next = NULL;
return list;
}
(2) 插入
关键代码 : s->next = p->next; p->next = s;
切忌这两个顺序不能调换
- s 即为new;
//按位置插入
int list_insert_at(struct node *list, int i, datatype data)
{
int j = 0;
struct node *p = list;
if (i < 0)
return -1;
while (p != NULL && j < i) { //寻找第i-1 个结点
p = p->next;
j++;
}
if (p == NULL || j > i)
return -2; //寻找第i 个元素不存在
struct node *new = (struct node *)malloc(sizeof(struct node)); // 生成新结点
if (new == NULL)
return -3;
new->data = data;
new->next = p->next; //将p的后继结点赋值给new的后继
p->next = new; // 将new赋值给p的后继
return 0;
}
//按序插入
int list_order_insert(struct node *list, datatype *data)
{
struct node *p = list, *q;
while (p->next->data < *data && p->next)
p = p->next;
q = (struct node *)malloc(sizeof(struct node));
if (q == NULL)
return -1;
q->data = *data;
q->next = p->next;
p->next = q;
return 0;
}
(3)删除
关键代码 p->next = p->next->next;
或 q = p->next; p->next = q->next;
最好用第二种方式,不然删除的结点的内存无法释放。
int list_delete(struct node *list, datatype *data)
{
struct node *p = list, *q;
while (p->next && p->next->data != *data)
p = p->next;
if (p->next == NULL)
return -1;
q = p->next;
p->next = q->next;
free(q);
return 0;
}
int list_delete_at(struct node *list, int i, datatype *data)
{
int j = 0;
struct node *p=list, *q;
p = list; //头指针
j = 1;
while (p->next && j < i) { //遍历寻找第i-1的元素,即要删除结点的前一个结点
p = p->next;
j++;
}
if ( !(p->next) || j>i )
return -1; // 第i个元素不存在
//p->next = p->next->next;
q = p->next;
p->next = q->next; // 将q的后继赋值给p的后继
*data= q->data; // 将q结点中的数据给e
free(q); // 释放内存
return 0;
}
(4)查找
int list_find(struct node *list, int i, datatype *data)
{
int j = 1; //j为计数器
struct node *p; //声明一结点指针p
p = list->next; // 让p指向链表的第一个结点
while (p && j<i) { //p 不为空或者计数器j还没有等于i时,循环继续
p = p->next; //让p指向下一个结点
++j;
}
if (p==NULL || j>i)
return -1; //第i元素不存在
*data= p->data; //取第i个元素的数据
return 0;
}
(5)整表创建
- 头插法
void create_list_head(struct node **head, int n)
{
struct node *p;
int i;
*head= (struct node *)malloc(sizeof(struct node));
(*head)->next = NULL; //先建立一个带头结点的单链表
for (i = 0; i<n; i++) {
p = (struct node *)malloc(sizeof(struct node));//生成新结点
p->data = i + 1;
p->next = (*head)->next;
(*head)->next = p; //插入到表头
}
}
- 尾插法
void create_list_tail(struct node **head, int n)
{
struct node *p, *r;
int i;
*head = (struct node *)malloc(sizeof(struct node));
r = *head;
for (i = 0; i<n; i++) {
p = (struct node *)malloc(sizeof(struct node)); //生成新结点
p->data = i+1;
r->next = p; //将表尾终端结点的指针指向新结点
r = p; //将当前的新结点定义为表尾终端结点
}
r->next = NULL;
}
(6)销毁
void list_destroy(struct node *list)
{
struct node *cur, *next;
for (cur=list->next; cur != NULL; cur = next) {
next= cur->next;
free(cur);
}
free(list);
}
(7)其他
//判断链表是否为空
int list_isempty(struct node *list)
{
if (list->next == NULL)
return -1;
return 0;
}
//显示
void list_show(struct node * list)
{
struct node *cur;
if (list_isempty(list) != 0)
return -1;
for (cur=list->next; cur!=NULL; cur=cur->next){
printf("%d ",cur->data);
}
printf("\n");
}
(8) 示例
#include <stdio.h>
#include <stdlib.h>
int main()
{
struct node *list=NULL;
int i, ret;
list = list_create();
if (list == NULL) {
printf("list create failed... \n");
exit(1);
}
for (i = 0; i < 5; i++) {
ret = list_insert_at(list, i, i+1);
if (ret != 0) {
printf("list insert failed... %d\n",ret);
break;
}
}
list_show(list);
datatype data = 5;
list_delete(list, &data);
list_show(list);
list_destroy(list);
}
2.2 无头结点的实现方式
- 无头结点不需要创建链表
// 插入,头插法
// list只是个头指针的地址,回传链表,不是头结点
int list_insert(struct node **list, elemtype elem)
{
struct node *new;
new = (struct node *)malloc(sizeof(struct node));
if (new == NULL)
return -1;
new->data = elem;
new->next = *list; //将p的后继结点赋值给s的后继
*list = new; // 将s赋值给p的后继
return 0;
}
//显示
void list_show(struct node *list)
{
struct node *cur;
for (cur = list; cur != NULL; cur = cur->next) {
printf("%d", cur->data);
}
}
// 删除,首部删除
int list_delete(struct node **list)
{
struct node *cur;
if (*list == NULL)
return -1;
cur = *list;
*list = (*list)->next;
free(cur);
return 0;
}
// 查找
struct node *list_find(struct node *list, elemtype e)
{
struct node *cur;
for (cur = list; cur != NULL; cur = cur->next) {
if (cur->data == e) {
return cur; //返回当前链表结点
}
}
return NULL;
}
//销毁
void list_destroy(struct node *list)
{
struct node *cur;
if (list == NULL)
return;
for (cur = list; cur != NULL; cur = list) {
list = cur->next;
free(cur);
}
}
2.3 小结
- 单向链表是后续链表的基础
- 单向链表随机访问比较差
三、单向循环链表
- 单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成个环,这种头尾相接的单链表称为单循环链表,简称循环链表( circular linked list )
- 循环链表和单链表的主要差异就在于循环的判断条件上,单链表判断循环结束为:node->next==NULL;而循环链表判断循环结束为:(node->next)等于头结点。
- 终端结点用尾指针rear指示,则查找终端结点是O(1),而开始结点,其实就是rear->next>next,其时间复杂也为O(1)。
// 创建循环链表, 不带头结点
struct node *create_list_head( int n)
{
struct node *list, *new, *cur;
int i = 1;
list = (struct node *)malloc(sizeof(struct node));
if (list == NULL)
return NULL;
//循环链表与单链表创建的唯一区别就是下面这行代码
//list->next = NULL; //先建立一个带头结点的单链表
list->next = list; //先建立一个带头结点的循环链表
list->data = i;
cur = list;
i++;
for (; i<n; i++) {
new= (struct node *)malloc(sizeof(struct node));//生成新结点
if (new == NULL)
return NULL;
new ->data = i;
new ->next = list;
cur->next = new; //插入到表头
cur = new;
}
return list;
}
//遍历链表
void get_elem(struct node *head, int index, datatype *e){
int i=1;
//因为头节点无数据所以p指向l->next
struct node *p=head->next;
//因为是循环链表,p没有为空的时候,所以判断条件应为p不指向头节点的时候
while ((p!=(head))&&i<index) {
p=p->next;
i++;
}
*e=p->data;
}
//插入
void insert_elem(struct node *head,int index, datatype e){
int i=1;
struct node *p=head->next;
while(p!=head && i<index){
p=p->next;
i++;
}
struct node *s;
s=(struct node *)malloc(sizeof(struct node));
s->data=e;
s->next=p->next;
p->next=s;
}
//删除
void list_delete(struct node **list, int n)
{
int i=1;
struct node *cur=*list, *node;
//因为是循环链表,p没有为空的时候,所以判断条件应为p的next不指向头节点的时候
while (cur != cur->next) {
while(i<n){
node = cur;
cur=cur->next;
i++;
}
printf("%d ", cur->data);
node->next = cur->next;
free(cur);
cur = node->next;
i = 1;
}
*list = cur;
printf("\n");
}
//清表
void clear_list(struct node *head){
struct node *q, *p=(head)->next;
//因为是循环链表,p没有为空的时候,所以判断条件应为p不指向头节点的时候
while(p != head){
q=p->next;
free(p);
p=q;
}
head->next=head;
}
void list_show(struct node *list)
{
struct node *p;
for (p = list; p->next != list; list = list->next)
printf("%d ",p->data);
printf("%d \n",p->data);
}
四、双向链表
- 在单链表的每个结点中,再设置一个指向其前驱结点的指针域,则为 双向链表( double linked list)。
- 所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。
- 双向链表存储结构
typedef int datatype ; //元素数据域类型可为任一类型,不局限于int
struct node
{
datatype data;
struct node *prev, *next;
};
- 双向链表的插入
// 头插法
s->prev= p; // 把p赋值给s的前驱,如图中①
s->next = p->next; // 把p->next 赋值给s的后继,如图中②
p->next->prev= s; //把要赋值给p->next的前驱,如图中③
p->next = s; //把要s赋值给p的后继,如图中④
// 尾插法
s->prev = p->prev;
s->next = p;
p->prev->next = s;
p->prev = s;
- 双向链表的删除
p->prev->next = p->next; //把p->next赋值给p->prior的后继,如图中①
p->next->prev= p->prev; //把p->prior赋值给p->next的前驱,如图中②
free(p); //释放结点
五、总结
总结1:若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。若需要频繁插入和删除时,宜采用单链表结构。