目录
-
链表
- 链表和数组一样都是存储数据,但是链表是非连续,非顺序的存储结构;
- 链表是灵活的内存动态管理(随机分配空间),删除创建结点非常方便;
- 链表组成:由一系列结点组成;
- 链表结点:实际上是结构体变量。
-
基本定义
typedef int ElemType; //定义基本数据类型
typedef struct st_link
{
ElemType data;
struct st_link* next;
}s_link;
链表结点包含两个部分:
- 存储数据元素的数据域(结构体);
- 存储下一个结点地址的指针域。
-
链表与数组的区别
- 链表:
- 无需一次性分配连续的存储空间,只需要分配n快结点存储区域,通过指针建立联系(malloc()/new());
- 不能随机访问任意一个元素,需要从头或从尾一次遍历;
- 没有固定大小限制;
- 删除和插入效率高;
- 内存空间连续或是不连续都可以;
- 查询较慢,需要一次遍历。
- 数组:
- 一次性分配连续的存储区域,int arr[60] = {0};
- 可以随机访问任意在数组内的元素;例如:arr[i],可以随机访问第i个元素;
- 插入和删除元素效率低下,需要大量移动元素;
- 查询较快,直接随意按下标访问任意一个元素;
- 数组需要一段连续的内存空间;
- 数组大小是固定的。
-
链表分类
-
静态链表和动态链表
- 链表按照线性表链式存储结构分为静态链表和动态链表;
- 静态链表:是在初始化的时候分配好足够的内存空间,存储空间是静态的,分配在栈上,模拟数组实现的,是顺序的存储结构;
- 动态链表:是动态申请内存的,每个节点物理地址不连续。
- 单向链表和双向链表
单向链表 | 双向链表 | |
next指针 | 每个元素只有一个指针只会下一个元素的地址 | 每个元素有两个指针只会上一个和下一个元素的地址 |
查询 | 较慢 | 较快,可以通过二分查找来提速 |
增删 | 较快 | 更快 |
存储效率 | 略高,除了存储元素,只会存储一个指针 | 较低,除了存储元素,需要存储两个指针 |
-
单向链表的基本操作
-
初始化数据元素
static void Init_link(s_link* link, ElemType data) { link->data = data; link->next = NULL; }
-
采用头插法插入元素
void Create_linkbyhead(s_link* link, ElemType data) //头插法 { s_link* L_newnode = malloc(sizeof(s_link)); Init_link(L_newnode, data); L_newnode->next = link->next; link->next = L_newnode; g_count++; }
head == NULL头节点为空
head != NULL头节点不为空
-
采用尾插法插入元素
void Create_linkbytail(s_link* link, ElemType data) //尾插法
{
s_link* L_newnode = malloc(sizeof(s_link));
Init_link(L_newnode, data);
s_link* cur = NULL;
cur = link;
if (NULL == link->next)
{
link->next = L_newnode;
g_count++;
}
else
{
while (cur->next != NULL) //遍历找到最后一各节点
cur = cur->next;
cur->next = L_newnode;
cur = L_newnode;
g_count++;
}
}
head == NULL头节点为空
head != NULL头节点不为空
-
依次遍历各个元素
void Foreach_link(s_link* link) //遍历
{
int i = g_count;
s_link* tmp = link;
if (0 == g_count)
return;
for (i = g_count; i > 0; i--)
{
if (tmp->next != NULL)
{
printf("%d\n", tmp->next->data);
tmp = tmp->next;
}
else
return;
}
}
-
按值删除节点
void delete_link_bydata(s_link* link, ElemType data)//按值删除元素
{
int flag = 0; //默认该元素不存在
int i = 0;
s_link* tmp = link;
s_link* first = link->next; //跳过头节点,指向第一个节点
s_link* second = first->next; //指向除头节点的第二个节点
if (0 == g_count)
printf("链表为空");
for (i = 0; i < g_count; ++i)
{
if (link->next == first && first->data == data) //first 在头节点之后的第一个节点
{
link->next = first->next;
tmp = first;
first = first->next;
if (second->next != NULL)
{
second = second->next;
}
free(tmp);
flag = 1;
g_count--;
}
else if (data == second->data && second->next != NULL)
{
tmp = second;
first->next = second->next;
second = second->next;
free(tmp);
g_count--;
flag = 1;
}
else if (second->next == NULL && data == second->data)
{
first->next = NULL;
free(second);
flag = 1;
g_count--;
}
else
{
first = first->next;
if (second->next != NULL)
second = second->next;
}
}
if (!flag)
printf("******Not have this data********\n");
}
按值删除一个链表的节点操作比较多,我们先定义一个指向head之后的first的节点和first节点之后的一个节点,然后依次遍历,找到需要删除的那个节点。即要找到删除的那个元素的前驱和后继,如果是最后一个元素则直接将其free().
-
按位删除节点
void delete_linkbypose(s_link* link, int pose) //按位删除元素 { if (pose > g_count || NULL == link) return; int i = 1; s_link* tmp = NULL; s_link* front = NULL; tmp = link->next; for (i = 1; i < pose; ++i) { front = tmp; tmp = tmp->next; } front->next = tmp->next; free(tmp); }
-
判断链表是否为空
static void Isempty(s_link* link) { if (NULL == link->next ) printf("链表为空\n"); else printf("链表不为空\n"); }
-
将链表置空
void setempty(s_link* link)
{
s_link* tmp = NULL;
s_link* destory = NULL;
if (NULL == link->next)
return;
for (tmp = link; tmp->next != NULL;)
{
destory = tmp;
tmp = tmp->next;
free(destory);
}
free(tmp);
link->next = NULL;
}
-
头文件
#ifndef __LINK_H__ #define __LINK_H__ #include <string.h> #include <stdio.h> #include <stdlib.h> typedef int ElemType; typedef struct st_link { ElemType data; struct st_link* next; }s_link; static void Init_link(s_link* link, ElemType data); void Create_linkbytail(s_link* link, ElemType data); void Create_linkbyhead(s_link* link, ElemType data); void Foreach_link(s_link* link); static int g_count = 0; void delete_link_bydata(s_link* link, ElemType data); static void Isempty(s_link* link); void delete_linkbypose(s_link* link, int pose); void setempty(s_link* link); #endif // ! __LINK_H__
-
源文件
/********************************************************************************* * Copyright: (C) 2020 shx * All rights reserved. * * Author: tianjincheng <[email protected]> * ********************************************************************************/ #include "link.h" static void Init_link(s_link* link, ElemType data) //初始换数据元素 { link->data = data; link->next = NULL; } void Create_linkbytail(s_link* link, ElemType data) //尾插法 { s_link* L_newnode = malloc(sizeof(s_link)); Init_link(L_newnode, data); s_link* cur = NULL; cur = link; if (NULL == link->next) { link->next = L_newnode; g_count++; } else { while (cur->next != NULL) //遍历找到最后一各节点 cur = cur->next; cur->next = L_newnode; cur = L_newnode; g_count++; } } void Create_linkbyhead(s_link* link, ElemType data) //头插法 { s_link* L_newnode = malloc(sizeof(s_link)); Init_link(L_newnode, data); L_newnode->next = link->next; link->next = L_newnode; g_count++; } void Foreach_link(s_link* link) //遍历 { int i = g_count; s_link* tmp = link; if (0 == g_count) return; for (i = g_count; i > 0; i--) { if (tmp->next != NULL) { printf("%d\n", tmp->next->data); tmp = tmp->next; } else return; } } /* 用双指针来实现按值来删除一个元素,即一个指向前驱,一个指向后继 */ void delete_link_bydata(s_link* link, ElemType data)//按值删除元素 { int flag = 0; //默认该元素不存在 int i = 0; s_link* tmp = link; s_link* first = link->next; //跳过头节点,指向第一个节点 s_link* second = first->next; //指向除头节点的第二个节点 if (0 == g_count) printf("链表为空"); for (i = 0; i < g_count; ++i) { if (link->next == first && first->data == data) //first 在头节点之后的第一个节点 { link->next = first->next; tmp = first; first = first->next; if (second->next != NULL) { second = second->next; } free(tmp); flag = 1; g_count--; } else if (data == second->data && second->next != NULL) { tmp = second; first->next = second->next; second = second->next; free(tmp); g_count--; flag = 1; } else if (second->next == NULL && data == second->data) { first->next = NULL; free(second); flag = 1; g_count--; } else { first = first->next; if (second->next != NULL) second = second->next; } } if (!flag) printf("******Not have this data********\n"); } void delete_linkbypose(s_link* link, int pose) //按位删除元素 { if (pose > g_count || NULL == link) return; int i = 1; s_link* tmp = NULL; s_link* front = NULL; tmp = link->next; for (i = 1; i < pose; ++i) { front = tmp; tmp = tmp->next; } front->next = tmp->next; free(tmp); } static void Isempty(s_link* link) { if (NULL == link->next ) printf("链表为空\n"); else printf("链表不为空\n"); } void setempty(s_link* link) { if (NULL == link->next) return; int i = 0; s_link* tmp = NULL; s_link* destory = NULL; for (tmp = link; tmp->next != NULL; ) { destory = tmp; tmp = tmp->next; free(destory); } free(tmp); link->next = NULL; } void test() { ElemType data; s_link* head = malloc(sizeof(s_link)); Init_link(head, -1); Isempty(head); Create_linkbytail(head, 1); Create_linkbytail(head, 2); Create_linkbytail(head, 3); Create_linkbytail(head, 4); Create_linkbytail(head, 5); printf("Before delete:\n"); Foreach_link(head); delete_link_bydata(head, 15); delete_link_bydata(head, 3); delete_link_bydata(head, 8); delete_linkbypose(head, 4); delete_linkbypose(head, 8); printf("After delete:\n"); Foreach_link(head); setempty(head); Isempty(head); Foreach_link(head); } int main(int argc, char** argv) { test(); return 0; }
-
运行结果分析
- 采用头插法插入元素
测试代码
void test()
{
ElemType data;
s_link* head = malloc(sizeof(s_link));
Init_link(head, -1);
Isempty(head); //判断是否为空
Create_linkbyhead(head, 6); //插入元素
Create_linkbyhead(head, 7);
Create_linkbyhead(head, 8);
Create_linkbyhead(head, 9);
Create_linkbyhead(head, 10);
Create_linkbyhead(head, 11);
Create_linkbyhead(head, 12);
Foreach_link(head); //遍历链表
Isempty(head); //在再次判断是否为空
}
运行结果,因为是采用头插法插入元素,所以当我们最先一个插入的元素会在最后输出,即先进后出(类似于栈)
2.采用尾插法插入元素
测试代码
void test()
{
ElemType data;
s_link* head = malloc(sizeof(s_link));
Init_link(head, -1);
Isempty(head);
Create_linkbytail(head, 6);
Create_linkbytail(head, 7);
Create_linkbytail(head, 8);
Create_linkbytail(head, 9);
Create_linkbytail(head, 10);
Create_linkbytail(head, 11);
Create_linkbytail(head, 12);
Foreach_link(head);
Isempty(head);
}
运行结果,因为是采用尾插法插入元素,所以当我们最先一个插入的元素会在最先输出,即先进先出(类似于队列)
3.按值删除元素
测试代码
void test()
{
ElemType data;
s_link* head = malloc(sizeof(s_link));
Init_link(head, -1);
Isempty(head);
Create_linkbytail(head, 1);
Create_linkbytail(head, 2);
Create_linkbytail(head, 3);
Create_linkbytail(head, 4);
Create_linkbytail(head, 5);
printf("Before delete:\n");
Foreach_link(head);
delete_link_bydata(head, 3);
delete_link_bydata(head, 8); //测试删除不在链表中的元素
printf("After delete:\n");
Foreach_link(head);
}
运行结果
4.按位删除元素
测试代码
void test()
{
ElemType data;
s_link* head = malloc(sizeof(s_link));
Init_link(head, -1);
Isempty(head);
Create_linkbytail(head, 1);
Create_linkbytail(head, 2);
Create_linkbytail(head, 3);
Create_linkbytail(head, 4);
Create_linkbytail(head, 5);
printf("Before delete:\n");
Foreach_link(head);
delete_linkbypose(head, 4);
delete_linkbypose(head, 8); //测试超过链表的最大位
printf("After delete:\n");
Foreach_link(head);
}
运行结果
-
使用Linux的valgrind来探测堆内存空间是否释放
5.清空链表(由于我们malloc许多的对空间我们置空链表是需要释放这些堆内存空间,我们可以使用Linux下的Valgrind工具来测试我们是否完全释放这些开辟在堆上的内存空间)
测试代码
void test()
{
ElemType data;
s_link* head = malloc(sizeof(s_link));
Init_link(head, -1);
Isempty(head);
Create_linkbytail(head, 1);
Create_linkbytail(head, 2);
Create_linkbytail(head, 3);
Create_linkbytail(head, 4);
Create_linkbytail(head, 5);
setempty(head);
Isempty(head);
Foreach_link(head);
}
运行结果
不调用链表置空函数
调用链表置空函数
linux下的valgrind的具体介绍可以看看博客:https://blog.csdn.net/qq_44045338/article/details/104730588