1 前言
上一篇文章主要描述线性表组成之一的顺序表的插入、删除、查找操作和C语言的实现。本文描述另一线性表【单链表】的操作和C语言实现。
2 链表头节点
一个单链表分为带头节点和不带头节点,带头节点与不带头节点,对于单链表的初始化、插入、删除等操作都不同,因此有必要了解这两类单链表。
2.1 带头节点链表
数据结构中,在单链表的第一个结点之前附设一个结点,它没有直接前驱(不存储有效数据),称之为头结点。带头节点的链表,头指针始终指向头节点。
带头节点的链表特点:
- 头指针存放的是头节点地址
- 头指针始终指向头结点,不因为节点的插入而改变
2.2 不带头节点链表
不带头节点的链表,头指针指向链表的第一个元素节点地址。与带头节点的链表不同,头指针的指向可能会因为节点的插入、删除操作,需重新改变头指针指向。
不带头节点的链表特点:
- 头指针存放的是链表第一个元素节点地址
- 头指针指向可能因为节点的插入、删除而改变
2.3 带头节点与不带头节点链表处理
根据带头节点和不带头节点的链表特点,两者在具体操作过程需要作不同的处理。
- 不带头节点的链表,对于第一个节点插入、删除需特别处理
- 带头节点节点的链表,通过判断头节点指针域是否为空来检查空表
if (head->next == NULL)
{
/* todo */
}
- 不带头节点的链表,通过判断头指针是否为空来检查空表
if (head == NULL)
{
/* todo */
}
- 实际场合一般使用带头节点的链表,这样对于链表的操作上更为简单,但需占用一个节点内存空间
- 根据使用习惯,对于带头节点的链表,链表的有效长度一般不包括头节点
文章以下部分描述的链表都是带头节点的链表。
3 单链表创建
单链表创建,一般是创建一个空的链表,这里我们创建一个带头节点的空链表。
link_list_t* create_link_list(void)
{
link_list_t* head = NULL;
head = (link_list_t*)malloc(sizeof(link_list_t));
if (head != NULL)
{
head->data=0; /* 头结数据域为空 */
head->pnext = NULL;
}
return head;
}
4 单链表清空与销毁
链表清空指的是删除链表有效元素节点,释放节点内存空间。链表销毁指的是删除所有节点,包括头结点,并释放节点内存空间。单链表清空与销毁都是遍历整个链表。
单链表清空伪代码:
int clear_link_list(link_list_t *list)
{
link_list_t *p = NULL;
link_list_t *q = NULL;
p = list->pnext;
while(p != NULL)
{
q = p->pnext;
free(p);
p = q;
}
list->pnext=NULL;
return 0;
}
单链表销毁伪代码:
int destory_link_list(link_list_t *list)
{
link_list_t *p = NULL;
while(list != NULL)
{
p = list->pnext;
free(list);
list = p;
}
list = NULL;
return 0;
}
5 单链表查找
单链表无论是插入还是删除操作,都需先查找到目标节点,所以首先描述单链表的查找操作。单链表查找有两种方式,分别是根据元素索引号和节点数据元素值查找,这两种方式都需要从链表头开始遍历链表,直至查找到指定节点。对于元素值查找,只适用于链表中存储的元素值都是唯一的情况,否则只能使用节点索引号查找。
如下图,查找一个带头节点单链表的第二个节点,可以通过索节点引号[1]查找或者通过唯一的节点数据元素[a1]查找。
C语言实现伪代码:
/* 通过节点索引号查找 */
link_list_t *get_link_list_node_pos(link_list_t *list, int pos)
{
link_list_t *p = NULL;
p = list;
while(pos>=0)
{
p = p->pnext;
if (p == NULL)
{
break;
}
pos--;
}
return p;
}
/* 通过节点数据元素查找 */
link_list_t *get_link_list_node_elem(link_list_t *list, int elem)
{
link_list_t *p = NULL;
p = list->pnext;
while(p!=NULL)
{
if (p->data == elem)
{
return p;
}
p = p->pnext;
}
return NULL;
}
6 单链表插入
单链表插入时间复杂度为O(1)。与顺序表一样,根据插入位置不同,单链表插入类型分为三种,不同的插入类型主要遍历链表节点的次数不同,即是效率不同。
- 表头插入,无需遍历链表
- 表尾插入,无需遍历链表
- 表中间插入,需遍历链表,即是查找操作
单链表插入步骤:
【1】查找到插入位置的前一节点,可通过节点索引号或者唯一节点数据元素查找
【2】申请待插入节点内存并赋值,节点指针域指向插入位置节点
【3】插入位置前一节点指针域指向插入节点
C语言实现伪代码:
int insert_link_list_node_pos(link_list_t *list, int value, int pos)
{
link_list_t *p = NULL;
link_list_t *node = NULL;
if (pos == 0)
{
p = list; /* 插入第一个节点位置 */
}
else
{
p = get_link_list_node_pos(list, pos-1); /* 获取插入位置的前一结点 */
}
if (p == NULL)
{
return -1;
}
node = malloc(sizeof(link_list_t));
if (node == NULL)
{
return -1;
}
/* 插入过程 */
node->data = value;
node->pnext = p->pnext;
p->pnext = node;
return 0;
}
7 单链表删除
单链表删除时间复杂度为O(1)。单链表删除与插入是一个相反的的过程,删除类型有三种,与插入类型对应。实质上,三种类型可以认为是一种类型,并且实现方式都是一样的。
- 表头删除
- 表尾删除
- 表中间删除
单链表删除步骤:
【1】查找到删除位置的前一节点,可通过节点索引号或者唯一节点数据元素查找
【2】删除位置前一节点指针域指向删除节点的下一节点
【3】释放删除节点内存
C语言实现伪代码:
int delete_link_list_node_pos(link_list_t *list, int pos)
{
link_list_t *p = NULL;
link_list_t *node = NULL;
if (pos == 0)
{
p = list; /* 删除第一个节点 */
}
else
{
p = get_link_list_node_pos(list, pos-1); /* 获取删除位置的前一结点 */
}
if ((p!=NULL) && (p->pnext!=NULL))
{/* 删除过程 */
node = p->pnext;
p->pnext = node->pnext;
free(node);
}
else
{
return -1;
}
}
8 实例
- 实现一个带头节点的单链表,头节点不纳入链表长度计算
- 提供单链表创建、长度查询、空表检查、插入、删除、查找、清空、销毁操作接口
- 提供节点索引号和节点数据元素查找实现方法
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <stdbool.h>
typedef struct _link_list
{
int data;
struct _link_list *pnext;/* 下一节点 */
}link_list_t;
link_list_t* create_link_list(void)
{
link_list_t* head = NULL;
head = (link_list_t*)malloc(sizeof(link_list_t));
if (head != NULL)
{
head->data=0; /* 头结数据域为空 */
head->pnext = NULL;
}
return head;
}
bool check_link_list_empty(link_list_t *list)
{
if (list == NULL)
{
return true;
}
if (list->pnext == NULL)
{
return true;
}
else
{
return false;
}
}
int destory_link_list(link_list_t *list)
{
link_list_t *p = NULL;
if ((list==NULL) || (list->pnext==NULL))
{
return 0;
}
while(list != NULL)
{
p = list->pnext;
free(list);
list = p;
}
list = NULL;
return 0;
}
int clear_link_list(link_list_t *list)
{
link_list_t *p = NULL;
link_list_t *q = NULL;
if ((list==NULL) || (list->pnext==NULL))
{
return 0;
}
p = list->pnext;
while(p != NULL)
{
q = p->pnext;
free(p);
p = q;
}
list->pnext=NULL;
return 0;
}
int get_link_list_capacity(link_list_t *list)
{
link_list_t *p = NULL;
int size = 0;
if (list == NULL)
{
return 0;
}
p = list->pnext;
while(p != NULL)
{
size++;
p = p->pnext;
}
return size;
}
link_list_t *get_link_list_node_pos(link_list_t *list, int pos)
{
link_list_t *p = NULL;
if ((list==NULL) || (pos<0))
{
return NULL;
}
p = list;
while(pos>=0)
{
p = p->pnext;
if (p == NULL)
{
break;
}
pos--;
}
return p;
}
link_list_t *get_link_list_node_elem_per(link_list_t *list, int elem)
{
link_list_t *p = NULL;
if ((list==NULL) || (list->pnext==NULL))
{
return NULL;
}
p = list->pnext;
while(p!=NULL)
{
if (p->pnext->data == elem)
{
return p;
}
p = p->pnext;
}
return NULL;
}
int insert_link_list_node_pos(link_list_t *list, int value, int pos)
{
link_list_t *p = NULL;
link_list_t *node = NULL;
if ((list==NULL) || (pos<0))
{
return -1;
}
if (pos == 0)
{
p = list; /* 插入第一个节点位置 */
}
else
{
p = get_link_list_node_pos(list, pos-1); /* 获取插入位置的前一节点 */
}
if (p == NULL)
{
return -1;
}
node = (link_list_t*)malloc(sizeof(link_list_t));
if (node == NULL)
{
return -1;
}
node->data = value;
node->pnext = p->pnext;
p->pnext = node;
return 0;
}
int insert_link_list_node_elem(link_list_t *list, int value, int elem)
{
link_list_t *p = NULL;
link_list_t *node = NULL;
if (list==NULL)
{
return -1;
}
p = get_link_list_node_elem_per(list, elem); /* 获取插入位置的前一节点 */
if (p == NULL)
{
return -1;
}
node = malloc(sizeof(link_list_t));
if (node == NULL)
{
return -1;
}
node->data = value;
node->pnext = p->pnext;
p->pnext = node;
return 0;
}
int delete_link_list_node_pos(link_list_t *list, int pos)
{
link_list_t *p = NULL;
link_list_t *node = NULL;
if ((list==NULL) || (pos<0))
{
return -1;
}
if (pos == 0)
{
p = list; /* 删除第一个节点 */
}
else
{
p = get_link_list_node_pos(list, pos-1); /* 获取删除位置的前一节点 */
}
if ((p!=NULL) && (p->pnext!=NULL))
{
node = p->pnext;
p->pnext = node->pnext;
free(node);
}
else
{
return -1;
}
}
int delete_link_list_node_elem(link_list_t *list, int elem)
{
link_list_t *p = NULL;
link_list_t *node = NULL;
if (list==NULL)
{
return -1;
}
p = get_link_list_node_elem_per(list, elem); /* 获取删除位置的前一节点 */
if ((p!=NULL) && (p->pnext!=NULL))
{
node = p->pnext;
p->pnext = node->pnext;
free(node);
}
else
{
return -1;
}
}
int main(int argc, char *argv[])
{
link_list_t *linklist = NULL;
link_list_t *ptemp;
int elem = 0;
int i;
/* 创建单链表 */
linklist = create_link_list();
/* 插入操作 */
insert_link_list_node_pos(linklist, 0, 0);
insert_link_list_node_pos(linklist, 1, 1);
insert_link_list_node_pos(linklist, 3, 2);
insert_link_list_node_pos(linklist, 5, 1);
printf("link list capacity:[%d]\n", get_link_list_capacity(linklist));
for(i=0; i<get_link_list_capacity(linklist); i++)
{/* 查找操作 */
ptemp = get_link_list_node_pos(linklist, i);
printf("link list node[%d]=%d\n", i, ptemp->data);
}
/* 删除操作 */
printf("delete link list node[2]\n");
delete_link_list_node_pos(linklist, 2);
printf("link list capacity:[%d]\n", get_link_list_capacity(linklist));
for(i=0; i<get_link_list_capacity(linklist); i++)
{/* 查找操作 */
ptemp = get_link_list_node_pos(linklist, i);
printf("link list node[%d]=%d\n", i, ptemp->data);
}
destory_link_list(linklist); /* 销毁单链表 */
}
编译执行
- 在Ubuntu16.04下执行结果
acuity@ubuntu:/mnt/hgfs/LSW/STHB/temp$ gcc link_list.c -o link_list
acuity@ubuntu:/mnt/hgfs/LSW/STHB/temp$ ./link_list
link list capacity:[4]
link list node[0]=0
link list node[1]=5
link list node[2]=1
link list node[3]=3
delete link list node[2]
link list capacity:[3]
link list node[0]=0
link list node[1]=5
link list node[2]=3