链表的知识

一、链表是啥

       链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构。

        链表由一系列节点(链表中每一个元素称为节点)组成,节点在运行时动态生成 (malloc),每个节点包括两个部分:一个是存储数据元素的数据域另一个是存储下一个节点地址的指针域

        链表由一个个节点构成,每个节点一般采用结构体的形式组织,例如:

struct ListNode {	
    int val;
	struct ListNode *next;
 };
 

      数据域:存放各种实际的数据,如:val

      指针域:存放下一节点的首地址,如:next

链表常见操作:插入、删除、反转。

二、和数据组做对比特点

1.链表是通过节点把离散的数据链接成一个表,通过对节点的插入和删除操作从而实现 对数据的存取。而数组是通过开辟一段连续的内存来存储数据,数组需要预留空间,这是数组和链表最大的区 别。

2.数组有起始地址和结束地址,链表有多种形式,而循环链表是一个圈,没有头和尾之分, 但是为了方便节点的插入和删除操作会人为的规定一个根节点。

3.数组数据查询简单,增加和删除困难;链表的特点是,查询相对于数组困难,增加和删除容易。

链表优点:

1.解决数组无法存储多种数据类型的问题

2.解决数组中,元素个数无法改变的限制

3.数组移动元素的过程中,要对元素进行大范围的移动时候,很耗时,效率不高。链表可是使用反转。

详细的可以见:(263条消息) 数据结构之数组和链表的区别_链表和数组的区别_Jasminexjf的博客-CSDN博客

三、链表分类

常用的链表有:单向链表、双向链表、循环链表。

四、链表操作

4.1 单向链表操作

初始化链表前需要在main函数中提前申请存储链表头节点的指针变量。

   需要加入头文件#include"stdlib.h"  才能用malloc申请空间。

操作程序如下:

#include
#include"stdlib.h"  //必须加入这个头文件

void AddListTill(int a);
void ScanList();
struct Node* FindNode(int a);
void AddListRand(int index, int a);
void DeleteListTail();
void DeleteListHead();
void DeleteListRand(int a);
//创建结构体,节点
struct Node{
	int a;				//数据域
	struct Node* next;	//指针域(指向节点的指针)
};

//全局定义链表头尾指针 方便调用
struct Node* head = NULL;   //定义一个头节点放着不动,就指向链表的头,等待着用它遍历和操作。
struct Node* end = NULL;    //定义一个尾巴节点,始终放在链表尾部,时刻等待着增加节点。

int main()
{
	//创建个结构体节点,存储找到的节点
	struct Node* Fnode;
	//1.创建链表
	//创建5个节点
	for (int i = 0; i < 6; i++)
	AddListTill(i);

	//2.打印链表的值,查看创建如何,内容如何
	ScanList();

	//3.找节点,找到节点后,把这个节点的数据打印出来
	Fnode = FindNode(3);
	printf("找到的节点内容是:%d\n",Fnode->a);

	//4.插入节点,按照要求插入节点
	AddListRand(3,3333);
	//打印链表的值,查看插入的咋样
	ScanList();

	//5.尾删除————删
	DeleteListTail();
	printf("删除尾巴后的链表\n");
	ScanList();
	//6.头删除————删
	DeleteListHead();
	//打印链表的值
	printf("删除头后的链表\n");
	ScanList();

	//7.删除指定结点
	DeleteListRand(4);
	//打印链表的值
	printf("删除指定节点后的链表\n");
	ScanList();

	return 0;
}

//7.删除指定结点
void DeleteListRand(int a)
{

	//链表判断 是不是没有东西
	if (NULL == head)
	{
		printf("链表没东西\n");
		return;
	}
	//链表有东西,找这个节点
	struct Node* temp = FindNode(a);
	if (NULL == temp)
	{
		printf("查无此点\n");
		return;
	}
	//找到了,且只有一个节点
	if (head == end)
	{
		free(head);
		head = NULL;
		end = NULL;
	}
	else if (head->next == end) //有两个节点
	{
		//看是删除头还是删除尾
		if (end == temp)
		{
			DeleteListTail();
		}
		else if (temp == head)
		{
			DeleteListHead();
		}
	}
	else//多个节点
	{
		//看是删除头还是删除尾
		if (end == temp)
			DeleteListTail();
		else if (temp == head)
			DeleteListHead();
		else	//删除中间某个节点
		{	//找要删除temp前一个,遍历
			struct Node* pt = head;
			while (pt->next != temp)
			{
				pt = pt->next;
			}
			//找到了
			//让前一个直接连接后一个 跳过指定的即可
			pt->next = temp->next;
			free(temp);

		}
	}


}



//6.头删除————删
void DeleteListHead()
{	//记住旧头
	struct Node* temp = head;
	//链表检测 
	if (NULL == head)
	{
		printf("链表为空\n");
		return;
	}

	head = head->next;//头的第二个节点变成新的头
	free(temp);

}


//5.尾删除————删
void DeleteListTail()
{
	if (NULL == end)
	{
		printf("链表为空,无需删除\n");
		return;
	}
	//链表不为空 
	//链表有一个节点
	if (head == end)
	{
		free(head);
		head = NULL;
		end = NULL;
	}
	else
	{
		//找到尾巴前一个节点
		struct Node* temp = head;
		while (temp->next != end)
		{
			temp = temp->next;
		}
		//找到了,删尾巴
	   //释放尾巴
		free(end);
		//尾巴迁移
		end = temp;
		//尾巴指针为NULL
		end->next = NULL;
	}

}



//4.在指定位置插入节点 ————在指定位置增,参数为加入节点的位置和节点的数据
void AddListRand(int index, int a)
{

	if (NULL == head)
	{
		printf("链表没有节点\n");
		return;
	}
	struct Node* pt = FindNode(index);//找到指定节点,返回这个节点的头指针
	if (NULL == pt)    //没有此节点
	{
		printf("没有指定节点\n");
		return;
	}
	//有此节点
	//创建临时节点,申请内存
	struct Node* temp = (struct Node*)malloc(sizeof(struct Node));
	//节点成员进行赋值
	temp->a = a;
	temp->next = NULL;
	//连接到链表上 1.找到的节点在尾部 2.找到的节点在中间 
	if (pt == end)
	{
		//尾巴的下一个指向新插入的节点
		end->next = temp;
		//新的尾巴
		end = temp;
	}
	else
	{
		// 先连后面 (先将要插入的节点指针指向原来找到节点的下一个)
		temp->next = pt->next;
		//后连前面
		pt->next = temp;
	}

}



//3.查找指定节点的内容,使用遍历所有节点的方法,返回值是找到的节点指针
struct Node* FindNode(int a)
{
	struct Node* temp = head;   //把头节点拷贝到临时头节点
	while (temp != NULL)     //对所有节点遍历
	{
		if (a == temp->a)
		{
			return temp;    //如果找到了自己想要的节点,就返回
		}
		temp = temp->next;
	}
	//没找到
	return NULL;
}



//2.打印所有节点内容函数
void ScanList()
{
	struct Node* temp = head;		//定义一个临时变量来指向头
	while (temp != NULL)
	{
		printf("%d\n", temp->a);
		temp = temp->next;		//temp指向下一个的地址 即实现++操作
	}

}
//1.创建a个节点链表函数
void AddListTill(int a)
{
	//创建一个节点,每次创建一个节点,都给这个节点分配一个空间,然后接在链子上
	struct Node* temp = (struct Node*)malloc(sizeof(struct Node));		//此处注意强制类型转换

	//节点数据进行赋值
	temp->a = a;
	temp->next = NULL;

	//连接分两种情况1.一个节点都没有2.已经有节点了,添加到尾巴上
	if (NULL == head)
	{

		head = temp;   //头节点指向第一个节点,创建第一个节点
		//	end=temp;
	}
	else
	{
		end->next = temp;   //创建头节点后,再有节点创建,节点都加在尾巴后面,尾插法
		//	end=temp;			//尾结点应该始终指向最后一个
	}
	end = temp;			//end结构体指针应该始终指向最后一个节点,= 可以翻译成指向,end指向新结点temp;
}

执行结果:

0
1
2
3
4
5
找到的节点内容是:3
0
1
2
3
3333
4
5
删除尾巴后的链表
0
1
2
3
3333
4
删除头后的链表
1
2
3
3333
4
删除指定节点后的链表
1
2
3
3333

C:\vs_work\01_Link\1_link\x64\Debug\1_Project1.exe (进程 1968)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

4.2 链表反转操作

操作题目为牛客第一题,链表反转

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param head ListNode类 
 * @return ListNode类
 */
 
struct ListNode* ReverseList(struct ListNode* head ) {
    // write code here
    struct ListNode* head_temp = head; //指向头节点,相当于head_temp可以对头节点指向的内存空间操作
    struct ListNode* new_head = NULL;   //新的头节点记录,用于最后反转的记录,反转后的头节点
    struct ListNode* pre_head = NULL;   //记录操作时候的前一个节点

    while(head_temp)
    {  
        new_head = head_temp;//新节点找到自己的位置,新节点一直往后移动
        head_temp = head_temp->next;  //头节点指向下一个节点,头节点也一直往后移动
        new_head->next = pre_head;  //新节点下一个节点指向前一个节点
        pre_head = new_head;     //更新前一个节点指向当前节点
    }
    return new_head;
}

      

参考文章:

(264条消息) C语言单链表的基本操作总结(增删改查)_万般滋味皆生活的博客-CSDN博客

(263条消息) 链表基础知识详解(非常详细简单易懂)_不秃也很强的博客-CSDN博客(264条消息) 建立一个单向链表的步骤(C语言)_单向链表建立_Mye_Strive的博客-CSDN博客

你可能感兴趣的:(数据结构,算法,链表,面试)