定义
????????链表是一种常见的基础数据结构,利用结构体指针来实现一个链表。链表可以动态的进行存储分配,也就是说,链表可以理解为是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。
????????链表都有一个头指针,一般以head来表示,存放的是一个地址。链表的每一个元素叫做节点,每个节点包含数据域和指针域;链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。总而言之,在链表中head指向第一个元素:第一个元素又指向第二个元素;直到最后一个元素,该元素的指针域不再指向其它元素,它的指针域部分放一个“NULL”(表示“空地址”),链表到此结束。总而言之,链表的头结点没有前驱,有一个后继,末尾节点没有后继,只有唯一的前驱,其余每一个节点只有唯一的一个前驱和后继。对于循环链表,即是将其尾结点的指针域指向头结点,这样就构成了一个闭环,即为单向循环链表。
?
? ? ? ?对链表的操作有许多,比如:链表的创建,修改,删除,插入,输出,排序,反序,清空链表的元素,求链表的长度等等。
对于链表,常见的一般是单向链表,其定义方式如下:
-
typedef struct student{ -
int ID; -
struct student *next; -
} LinkList;
?此处定义了一个学生结构体,并将其重命名为LinkList,对于每一个学生节点,其内部包含了他的ID信息和下一个学生节点的地址,存储这样一系列的节点的结构就被称之为链表。
实例
下面以一个实例对单向链表进行讲解?:
#ifndef _LINKLIST_H
#define _LINKLIST_H
typedef int linklist_data_t;
typedef struct linklist{
linklist_data_t data;
struct linklist* next;
}lkl_node, *lkl_pnode;
//创建头节点
lkl_pnode create_linklist();
//插入
int insert_linklist(lkl_pnode H, int pos, linklist_data_t data);
//打印
int show_linklist(lkl_pnode H);
//判空
int empty_linklist(lkl_pnode H);
//获取链表长度
int getlen_linklist(lkl_pnode H);
//删除
int delete_linklist(lkl_pnode H, linklist_data_t data); //按值删除
int delete1_linklist(lkl_pnode H, int pos);//按位置删除
//查询
linklist_data_t search_linklist(lkl_pnode H, int pos); //按位置查询值
int search1_linklist(lkl_pnode H, linklist_data_t data);//按值查位置
//修改
int change_linklist(lkl_pnode H, int pos, linklist_data_t data); //按位置修改值
int change1_linklist(lkl_pnode H, linklist_data_t old_data, linklist_data_t new_data);
//清空
int clean_linklist(lkl_pnode H);
//销毁
int destroy_linklist(lkl_pnode *H);
//链表的逆序
int linklist_reverse(lkl_pnode H);
//链表的排序
int linklist_sort(lkl_pnode H);
#endif
?????????在上述头文件中,我们定义了一个存储int型数据的节点结构体?,使用typedef int linklist_data_t;的原因是如果直接在结构体内部声明data的数据类型为int,若后期需要存储其他类型的数据如char、double、float等,就需要去结构体内部更改,代码的复用性就大大降低了。而使用上面这种方式,只需要将typedef int linklist_data_t;改为typedef char?linklist_data_t;就可以实现从存储int型转换为存储char型或则其他类型。
????????同时,在头文件中我们声明了对链表实现某些操作的功能函数,比如:链表的创建,修改,删除,插入,输出,排序,反序,清空链表的元素,求链表的长度等等。
接下来是这些功能函数的实现方法:
1、链表的创建
//创建头节点
lkl_pnode create_linklist()
{
//创建堆区空间
lkl_pnode H = (lkl_pnode)malloc(sizeof(lkl_node));
if (NULL == H)
{
printf("malloc is default\n");
return NULL;
}
H->next = NULL; //一开始是没有其他节点的,所以头节点指向NULL
H->data = -1;//对于头结点的数据域,不使用,初始化为-1,以便用户识别
return H;
}
对于链表的创建,只需要创建一个头结点即可,后续的其他操作均是在头结点后面进行。对于创建函数,返回链表的头结点。
2、链表的判空
//判空
int empty_linklist(lkl_pnode H)
{
if (H->next == NULL)
return 0;
else
return -1;
}
?因为头结点的指针域存储的是下一个节点的地址,故若头结点指针域为空,则代表该链表为空。
3、获取链表对的长度
int getlen_linklist(lkl_pnode H)
{
int count = 0;
while (H->next)
{
H = H->next;
count++;
}
return count;
}
?在获取链表的长度时需要注意的是,头结点并不算做是有效节点;故应该从H->next即第一个节点开始计数。
4、插入
//插入
int insert_linklist(lkl_pnode H, int pos, linklist_data_t data)
{
//判断插入位置是否合法
if (pos < 0 || pos > getlen_linklist(H))
{
printf("pos is default\n");
return -1;
}
//找到插入位置的前一个节点的位置
while (pos--)
{
H = H->next;
}
//插入,头插
lkl_pnode new = create_linklist();
new->data = data;
new->next = H->next;
H->next = new;
return 0;
}
此处实现的是按位置插入,核心点是使用while循环找到要插入的位置的前驱节点,然后使用头插的方式将该节点插入。
5、打印?
//打印
int show_linklist(lkl_pnode H)
{
if (0 == empty_linklist(H))
{
printf("H is empty\n");
return -1;
}
//遍历
while (H->next)
{
H = H->next;
printf("dat=%d ", H->data);
}
printf("\n");
}
打印的思想比较简单,即遍历一遍链表,挨个打印即可
6、删除?
删除有两种方式,即按位置删除和安置删除
首先是按值删除
int delete_linklist(lkl_pnode H, linklist_data_t data) //按值删除
{
if (0 == empty_linklist(H))
{
printf("H is empty\n");
return -1;
}
lkl_pnode p = H;
lkl_pnode q = H->next;
while (q)
{
if (q->data == data)
{
p->next = q->next;
free(q);
q = p->next;
continue;
}
p = q;
q = q->next;
}
}
安值删除需要借助两个辅助指针一前一后,若后一个指针的数据域与要删除的值相等,则前一个指针的指针域直接指向后一个指针的指针域(?p->next = q->next;);即断开了要删除的节点,然后释放掉该节点即完成删除。
按位置删除
int delete1_linklist(lkl_pnode H, int pos)//按位置删除
{
if (0 == empty_linklist(H))
{
printf("H is empty\n");
return -1;
}
if (pos < 0 || pos >= getlen_linklist(H))
{
printf("pos is default\n");
return -1;
}
//找到要删除的前一个节点位置
while (pos--)
{
H = H->next;
}
//保存要删除的节点的位置
lkl_pnode p = H->next;
//连线
H->next = p->next;
//释放空间
free(p);
p = NULL;
return 0;
}
按位置删除比较简单,找到该位置的前驱节点,断开连接,释放空间即可。
7、查询?
同上,查询也分为按值查询和按位置查询,其实现代码如下:
//查询
linklist_data_t search_linklist(lkl_pnode H, int pos) //按位置查询值
{
if (0 == empty_linklist(H))
{
printf("H is empty\n");
return -1;
}
if (pos < 0 || pos >= getlen_linklist(H))
{
printf("pos is default\n");
return -1;
}
while (pos--)
{
H = H->next;
}
return H->next->data;
}
int search1_linklist(lkl_pnode H, linklist_data_t data)//按值查位置
{
if (0 == empty_linklist(H))
{
printf("H is empty\n");
return -1;
}
int pos=0;
H = H->next;
while (H)
{
if (H->data == data)
break;
pos++;
H = H->next;
}
return pos;
}
?8、修改
修改也有两种方式,按位置修改,安置修改;
//修改
int change_linklist(lkl_pnode H, int pos, linklist_data_t data) //按位置修改值
{
if (0 == empty_linklist(H))
{
printf("H is empty\n");
return -1;
}
if (pos < 0 || pos >= getlen_linklist(H))
{
printf("pos is default\n");
return -1;
}
while (pos--)
{
H = H->next;
}
H->next->data = data;
return 0;
}
int change1_linklist(lkl_pnode H, linklist_data_t old_data, linklist_data_t new_data)//按值修改
{
if (0 == empty_linklist(H))
{
printf("H is empty\n");
return -1;
}
H = H->next;
while (H)
{
if (H->data == old_data)
H->data = new_data;
H = H->next;
}
}
?9、清空
//清空
int clean_linklist(lkl_pnode H)
{
if (0 == empty_linklist(H))
{
printf("H is empty\n");
return -1;
}
while (empty_linklist(H))
{
delete1_linklist(H, 0);
}
return 0;
}
?这里需要注意两点:一是对链表的清空,即释放除头结点以外的所有节点,故可以调用删除函数来挨个删除元素即可;二是链表清空后,其头结点还在,热然可以对齐进行插入等操作;
10、销毁
//销毁
int destroy_linklist(lkl_pnode* H)
{
if (0 == empty_linklist(*H))
{
free(*H);
*H = NULL;
}
else
{
clean_linklist(*H);
free(*H);
*H = NULL;
}
return 0;
}
对于链表的销毁,也有两点需要注意,首先是链表的销毁即释放掉链表的所有节点,包括头结点,故链表销毁后不可以再使用;其次是,对于链表的销毁,该函数需要进行地址传递,才能通过形参来改变实参,即销毁掉实参链表,否则只是对链表进行了清空,链表头结点实际还存在,要想完全释放掉链表,主函数中还需要再free一次并置空。
11、链表的逆序?
链表的逆序主要思想为将头结点与后面的节点断开,分成两个链表,然后一一取出后面节点组成的链表节点对头结点链表做头插,即可完成链表的逆序。
//链表的逆序
int linklist_reverse(lkl_pnode H)
{
if (0 == empty_linklist(H))
{
printf("H is empty\n");
return -1;
}
lkl_pnode p, q;
p = q = H->next;
H->next = NULL;
while (q != NULL)
{
q = q->next;
insert_linklist(H, 0, p->data);
p = q;
}
}
12、链表的排序
对链表进行排序的思想与逆序相似,首先也是断开头结点分成两个链表,然后一一取出后面链表的节点与头结点链表的每一个节点进行比较后插入,循环往复实现排序。
本例实现的是从小到大的排序:
//链表的排序
int linklist_sort(lkl_pnode H)
{
if (0 == empty_linklist(H))
{
printf("H is empty\n");
return -1;
}
lkl_pnode p, q,t;
q = H->next;
H->next = NULL;
while (q != NULL)
{
p = q;
q = q->next;
t = H;
while (t)
{
if (t->next == NULL)
{
p->next = t->next;
t->next = p;
break;
}
if (t->next->data >= p->data)
{
p->next = t->next;
t->next = p;
break;
}
else
{
t = t->next;
}
}
}
}
总结?
以上便是对链表所有操作函数的实现,下面看一下实现效果:
int main()
{
lkl_pnode H = create_linklist();
puts("-------------------insert 0-9--------------");
int i;
for (i = 0; i < 10; i++)
{
insert_linklist(H, 0, i);
}
show_linklist(H);
puts("\n");
puts("-----------------delete pos=2--------------");
delete1_linklist(H, 2);
show_linklist(H);
puts("\n");
puts("-----------------change pos=2 data=666-----");
change_linklist(H, 2, 666);
show_linklist(H);
puts("\n");
puts("-----------------change pos=3 data=666-----");
change_linklist(H, 3, 666);
show_linklist(H);
puts("\n");
puts("-----------------change old=0 new=777-----");
change1_linklist(H, 0, 777);
show_linklist(H);
puts("\n");
puts("----------------- 排序----------------------");
linklist_sort(H);
show_linklist(H);
puts("\n");
puts("-----------------delete val=777--------------");
delete_linklist(H, 777);
show_linklist(H);
puts("\n");
puts("-----------------search pos=4 ---------------");
printf("data=%d\n", search_linklist(H, 4));
puts("\n");
puts("-----------------search val=3 ---------------");
printf("pos=%d\n", search1_linklist(H, 3));
puts("\n");
puts("-----------------change pos=2 data=888-----");
change_linklist(H, 2, 888);
show_linklist(H);
puts("\n");
puts("-----------------change pos=3 data=999-----");
change_linklist(H, 3, 999);
show_linklist(H);
puts("\n");
puts("-----------------逆序-----------------------");
linklist_reverse(H);
show_linklist(H);
puts("\n");
puts("----------------- 排序----------------------");
linklist_sort(H);
show_linklist(H);
puts("\n");
puts("-----------------clean-----------------------");
clean_linklist(H);
show_linklist(H);
puts("-----------------destroy---------------------");
destroy_linklist(&H);
printf("H=%p\n", H);
}
上面是主函数的测试程序,包含了所有设计函数的使用,以下是输出结果:
?
|