( 从零开始的数据结构生活)一、链表

可能有很多人已经学习了数据结构,但是还是不那么精通,今天我们一起学习链表。
开始这篇文章之前,代码部分我引用了印度老哥Harsha Suryanarayana的代码,并对学习过程中有问题的点进行描述。
本文章分为6个部分:
1.头部插入一个节点
2.任意位置插入或删除一个节点
3.反转链表之迭代
4.递归打印链表
5.反转链表之递归
6.双向链表


1.头部插入一个节点
代码:


结构体

struct Node
{
	int data;
	struct Node* next;
};
struct Node* head;

插入函数:

void Insert(int x)
{
	struct Node* temp =(strcut Node*)malloc(sizeof(struct Node));
	temp->data = x;
	temp->next =head;
	head =temp;
}

代码解释:
首先在堆上分配空间;
其次进行赋值,并且把头指针的地址传入 temp->next 因为 head 的地址是NULL,那么此时 temp->next 的地址因为是NULL;
最后我们把 temp 的地址传入 头指针 head。


输出函数

void Print()
{
	struct Node* temp = head;
	while(temp != NULL)
	{
		printf("%d ",temp->data);
		temp = temp->next;
	}
	printf("\n");
}

代码解释:
首先引用一个临时变量,避免失去头节点;
其次进行值的打印,如果此时 temp 的值不为NULL,那么进行循环、打印。


主函数:

int main()
{
	head = NULL;
	printf(''需要多少个数字");
	int n,i,x;
	sxanf("%d",&n);
	for(i=0,i<n;i++)
	{
		printf("输入值");
		scanf("%d",&x);
		Insert(x);
		Print();
	}
}

2.任意位置插入或删除一个节点


(1)任意位置插入一个节点
插入函数:

void Insert(int data , int n)
{
	Node*  temp1 = new Node();
	temp1->data = data;
	temp1->next = NULL;	
	if(n == 1)
	{
		temp1->next = head;
		head = temp1;
		return;
	}
	Node* temp2 = head;
	for(int i=0;i<n-2:i++)
	{
		temp2 = temp2->next;
	}
	temp1->next = temp2->next;
	temp2->next = temp1;
}

代码解释:
void Insert(int data , int n) 中 data 作为值的传递,n 作为链表中位置的传递;
首先,new函数是C++的分配堆的空间,与笔记1.没有区别;
其次,我们对temp1->data进行赋值,temp1->next指向NULL;
第三,如果我们希望它作为第一个节点,那么我们让 temp1->next 指向头节点所指向的,头节点 head 指向temp1;
如果不是作为第一个节点,而是插入其他位置,那么我们需要引入另一个临时变量 temp2 代替头函数,因为我们不能改变头函数,后续我们还需要头函数重新进入该链表;
接下来,我们需要让这个链表从 temp2 开始循环,循环((n-2)-0)+1次,即n-1次;
现在,程序已经到达第n-1个节点,我们需要将 temp1 插入到第n个位置,只需要断开原来第n-1个节点至第n个节点的位置,然后将第n-1个节点连接 temp1 ,将 temp1 连接原来的第n个节点,现在我们的新链表诞生了, temp1 也在第n个节点上了。


输出函数

void Print()
{
	struct Node* temp = head;
	while(temp != NULL)
	{
		printf("%d ",temp->data);
		temp = temp->next;
	}
	printf("\n");
}

与前文的输出函数一样。


主函数:

int main()
{
	head = NULL;
	Insert (x,y);
	Print();
}

代码解释:
这里的x,y 是准确的值,不是变量;
下文如若没有说明,可自行理解。


(2)任意位置删除一个节点
接下来,我们进行任意位置节点的删除,且Print函数和Insert函数不在赘述。


删除函数:

void Delete(int n)
{
	struct Node* temp1 = head;
	if(n == 1)
	{
		head = temp1->next ;
		free(temp1);
		return;
	}
	int i;
	for(int i=0;i<n-2;i++)
		temp1 = temp1->next;
	struct Node* temp2 = temp1->next;
	temp1->next = temp2->next;
	free(temp2);
}

删除函数与插入函数的思想很相似:
我们对temp1代替头节点;
如果我们希望删除第一个节点,那么我们让 temp1->next 指向头节点所指向的,此时释放 temp1 所占用的堆空间就ok了;
如果不是作为第一个节点,而是删除指定位置,我们需要让这个链表从 temp1 开始循环,依旧循环n-1次,到达第n-1个节点,此时引入另一个临时变量 temp2 指向 temp1->next 所指向的节点,此时 temp2 代替了temp1中第n个节点
我们需要将第n-1个节点的 temp1 ->next 指向第n+1个节点,所以需要让第n个节点的temp1->next赋值给第n-1个节点的temp1->next;
最后释放temp2所占用的堆空间,现在我们的新链表诞生了,第n个节点也删除了。


主函数:

int main()
{
	head =NULL;
	Insert(x,y);
	int n;
	scanf("%d",&n);
	Delete(n);
	Print();
}

代码解释:
其中 n 为需要删除的节点位置。


3.反转链表之迭代


结构体:

struct Node 
{
	int data;
	struct Node* next;
};
struct  Node* head;

void Reverse()
{
	sruct Node* crruent ,* prev,* next;
	current = head;
	prev = NULL;
	while(current != NULL)
	{
		next = current->next;
		current->next = prev;
		prev = current;
		current = next;
	}
	head =prev;
}

current现在为链表;
我们可以把该函数看作一个工具,对链表里面的所有节点进行改造;
我们需要反向目前节点与上一个的关系,让第n个节点指向第n-1个节点(这里对应while循环里面的第1,2行);
接下来,我们需要改造第n+1个节点,所以我们需要把 current 传给 prev,把 next 传给 current(这里对应while循环里面的第3,4行)。


这里我目前不在写主函数和其他函数。


4.递归打印链表
打印函数与前面的区别:
在这里,我不再使用while循环进行打印,我用递归的方法进行打印,这样我可以在不使用反转函数的前提下,依然打印出反转的链表。
代码:


Print函数进行正向打印:

void Print(struct Node* p)
{
	if (p == NULL)
		return;
	printf("%d ",p->data);
	Print(p->next);
}

RPrint函数进行反向打印:

void RPrint(struct Node* p)
{
	if (p == NULL)
		return;
	RPrint(p->next);
	printf("%d ",p->data);
}

代码解释:
正向霍纳好理解,我们直接进入RPrint函数:
首先判断该指针是否为空指针,是则退出,否则进行递归直到最后一个节点;
现在,程序已经到最后一个节点了,假设有n个节点,我们对其进行打印,然后返回第n-1个节点,打印,再返回第n-2个节点,打印,如此循环直至打印完第一个节点。
(如果有不理解的,那一定是递归没有掌握,我建议重新学习递归再看RPrint函数,相信你会大有长进的)


5.反转链表之递归
使用递归进行反转链表,与迭代的区别:
(1)迭代:复反馈过程的活动,每一次迭代的结果会作为下一次迭代的初始值。
(2)递归:一直调用自己。
(3)代码:


结构体:

struct Node 
{
	int data;
	struct Node* next;
};
struct  Node* head;

反转函数

void Reverse(strcut Node* p)
{
	if (p->next == NULL)
	{
		head = p;
		return;
	}
	Reverse(p->next);
	struct Node* q = p->next;
	q->next = p;
	p->next = NULL;
}

代码解释-void Reverse:
如果 p->next 不为NULL,则跳过 if 语句,进入 Reverse 函数,进行递归,直至 p->next 为NULL,结束递归,执行 struct Node* q =p->next 这一语句。
我们现在假设链表有5个节点
现在进行第一次跳出,头指针的值为p[4],q[0]为p[4],q[1]为p[3],p[4]为NULL;
进行第二次跳出,q[1]为p[3],q[2]为p[2],p[3]为NULL;
直至q[4]为p[0],那么现在有q链表,而p链表的所有值已经是NULL。


6.双向链表
(1)定义:它的每个数据结点中都有两个指针 ,分别指向直接后继和直接前驱。
(2)特点:它需要3个部分,第一个部分指向前一个数据的地址,第二个部分为数据本身,第三个部分指向后一个数据的地址。
(3)优点:可以反向查询。
(4)缺点:查询慢,如果要访问链表中一个元素,就需要从第一个元素遍历查找;需要额外的内存
(5)代码:分为两个部分,插入,删除。


构建一个结构体:

struct Node 
{
	int data;
	struct Node* next;
	struct Node* prev;
};
struct Node* head;

创建一个新节点:

struct Node* GetNewNode(int x);
{
	struct Node* newNode = (struct Node*) malloc (sizeof (struct Node));
	newNode->data = x;
	newNode->prev = NULL;
	newNode->next = NULL;
	return newNode;
}

头部插入:

void InsertAtHead(int x)
{
	struct Node* newNode = GetNewNode(x);
	if (head == NULL)
	{
		head = newNode;
		return;
	}
	head->prev = newNode;
	newNode->next = head;
	head = newNode;
}

代码解释-void InsertAtHead:
我们先将 函数GetNewNode 传进 InsertAtHead 中,先进行判断,如果链表中只有头节点,那么链表对头节点下一个传出的就是newNode,我们称之为newNode1,接下来退出这个函数。
如果链表已经有一个及以上的节点了,那么我们 head->prev 即就是 newNode1->prev 将指向 newNode2 ,而 newNode2 通过指向头节点,头节点指向 newNode1 来指向newNode1 ,我们此时再将头节点指向 newNode2 ,这样就插入一个头节点。


正向打印:

void Print()
{
	struct Node* temp = head;
	printf("Forward:");
	while(temp != NULL)
	{
		printf("%d ",temp->data);
		temp = temp->next;
	}
	printf("\n");
}

反向打印:

void RevesrePrint()
{
	struct Node* temp = head;
	if(temp == NULL)
		return;
	while(temp->next!=NULL)
	{
		temp = temp->next;
	}
	printf("Reverse:");
	while(temp != NULL)
	{
		printf("%d ",temp->data);
		temp = temp->prev;
	}
	printf("\n");
}

正向打印不在解释,我们对反向打印进行学习:
首先建立一个头指针,用 temp 命名;
接下来进入if判断,避免空指针的出现;
其次进入while循环,目的是到达最后一个节点;
最后,利用temp->prev进行打印。


主函数:

main()
{
	head = NULL;
	InsertAtHead(x);
	Print();
	ReversePrint();
}

接下来我们进行尾部节点的插入:
前提:
我们需要构建一个全局变量 temp ,主要用来在尾部节点相连时进行中间量。

void InsertAtTail(int x)
{
	struct Node* newNode = GetNewNode(x);
	if (head == NULL)
	{
		head = newNode;
		temp = head;
		return;
	}
	temp->next = newNode;
	newNode->prev = temp;
	temp = newNode;
 }

代码的思路与头部插入一样。


最后以上内容就是这么多,后续我可能会再b站发一起视频配合这个进行讲解。
多加练习,会很容易了解数据结构的。
Harsha Suryanarayana的熟肉
同时也感谢up主fengmuzi2003的翻译。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值