C基础之链表

目录

链表

基本定义

链表与数组的区别

链表分类

单向链表的基本操作

初始化数据元素

采用头插法插入元素

采用尾插法插入元素

依次遍历各个元素

按值删除节点

按位删除节点

判断链表是否为空

将链表置空

头文件

源文件

运行结果分析

使用Linux的valgrind来探测堆内存空间是否释放


 

  • 链表

  1. 链表和数组一样都是存储数据,但是链表是非连续,非顺序的存储结构;
  2. 链表是灵活的内存动态管理(随机分配空间),删除创建结点非常方便;
  3. 链表组成:由一系列结点组成;
  4. 链表结点:实际上是结构体变量。
  • 基本定义


typedef int    ElemType; //定义基本数据类型

typedef struct st_link
{
	ElemType         data;
	struct st_link* next;
}s_link;

链表结点包含两个部分:

  1. 存储数据元素的数据域(结构体);
  2. 存储下一个结点地址的指针域。
  • 链表与数组的区别

  • 链表:
  1. 无需一次性分配连续的存储空间,只需要分配n快结点存储区域,通过指针建立联系(malloc()/new());
  2. 不能随机访问任意一个元素,需要从头或从尾一次遍历;
  3. 没有固定大小限制;
  4. 删除和插入效率高;
  5. 内存空间连续或是不连续都可以;
  6. 查询较慢,需要一次遍历。
  • 数组:
  1. 一次性分配连续的存储区域,int arr[60] = {0};
  2. 可以随机访问任意在数组内的元素;例如:arr[i],可以随机访问第i个元素;
  3. 插入和删除元素效率低下,需要大量移动元素;
  4. 查询较快,直接随意按下标访问任意一个元素;
  5. 数组需要一段连续的内存空间;
  6. 数组大小是固定的。
  • 链表分类

  •  静态链表和动态链表

  1. 链表按照线性表链式存储结构分为静态链表和动态链表;
  2. 静态链表:是在初始化的时候分配好足够的内存空间,存储空间是静态的,分配在栈上,模拟数组实现的,是顺序的存储结构;
  3. 动态链表:是动态申请内存的,每个节点物理地址不连续。
  • 单向链表和双向链表
  单向链表 双向链表
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;
    }
  • 运行结果分析

  1. 采用头插法插入元素

测试代码

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 

发布了47 篇原创文章 · 获赞 37 · 访问量 3682

猜你喜欢

转载自blog.csdn.net/qq_44045338/article/details/105106696