Linked List链表

一、前言

内存存储方式

内存通常分为:Heap ,Stack ,Static/Global ,Code(Text),一部分存储所有需要被执行的指令,另一部分存储全局变量,栈存储所有有关函数调用执行的信息和所有局部变量,这三个部分的大小是固定的,最后的堆或空闲存储是不固定的。 


二、插入

1)在头部插入一个节点

设置全局变量head时

#include <iostream>
using namespace std;
struct Node
{
	int data;
	struct Node* next;
};
struct Node* head;//全局变量 
void Insert(int x)
{
	Node* temp=(Node*)malloc(sizeof(struct Node));
	temp->data=x;
	temp->next=head; 
	head=temp;
}
void Print()
{
	struct Node* temp=head;
	while(temp!=NULL)
	{
		cout<<" "<<temp->data;
		temp=temp->next;
	}
	cout<<endl;
}
int main()
{
	head=NULL;//链表为空 
	int n,x;//元素的个数和元素 
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>x;
		Insert(x);
		Print();//不带参数 
	}
}

如果不设置全局变量head,则代码修改为:

//Linked List:Inserting a node at biginning
#include <iostream>
using namespace std;
struct Node
{
	int data;
	struct Node* next;
};
Node* Insert(Node* head,int x)
{
	Node* temp=(Node*)malloc(sizeof(struct Node));
	temp->data=x;
	temp->next=head; 
	head=temp;
	return head;
}
void Print(Node* head)
{
	while(head!=NULL)
	{
		cout<<" "<<head->data;
		head=head->next;
	}
	cout<<endl;
}
int main()
{
	Node* head=NULL;//链表为空 
	int n,x;//元素的个数和元素 
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>x;
		head=Insert(head,x);
		Print(head);//不带参数 
	}
}

 2)在任意位置插入一个节点

考虑到链表为空,或插入位置无效。

如果链表有3个元素,则可以在1、2、3、4位置上插入。

在n位置插入,就要把原来n位置的next指针赋值给新的x,新的x的地址赋值给原来该位置的指针

#include <iostream>
using namespace std;
struct Node
{
	int data;
	struct Node* next;
};
struct Node* head;
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 Print()
{
	struct Node* temp=head;
	while(temp!=NULL)
	{
		cout<<" "<<temp->data;
		temp=temp->next;
	}
	cout<<endl;
}
int main()
{
	head=NULL;//empty list 
	Insert(2,1);//: 2
	Insert(3,2);//: 2,3
	Insert(4,1);//: 4,2,3
	Insert(5,2);//: 4,5,2,3
	Print();
}

三、删除

在任意位置删除一个节点

思路:修理连接,释放内存

#include <iostream>
using namespace std;
struct Node
{
	int data;
	Node* next;
}; 
struct Node* head;
void Insert(int data)//默认在头部插入 
{
	Node* temp=new Node();
	temp->data=data;
	temp->next=head;
	head=temp;
}
void Print()
{
	Node* temp=head;
	while(temp!=NULL)
	{
		cout<<" "<<temp->data;
		temp=temp->next;
	}
	cout<<endl;
}
void Delete(int n)
{
	Node* temp=head;
	if(n==1)
	{
		head=temp->next;
		free(temp);
		return;
	}
	for(int i=0;i<n-2;i++)
	{
		temp=temp->next;
	}
	Node* temp1=temp->next;
	temp->next=temp1->next;
	free(temp1);
}
int main()
{
	head=NULL;
	Insert(2);
	Insert(3);
	Insert(4);
	Insert(5);//: 5 4 3 2
	Print();
	int n;
	cin>>n;
	Delete(n);
	Print();
}

四、打印 

用递归法打印一个链表(顺序、逆序)

tips:如果不用全局变量head时代码的改动?Insert插入函数会有一个返回值,我们把这个返回值赋值给主函数的局部变量head。

顺序打印的思路?首先我们判断递归是否要进行,也就是链表是否为空,我们令temp=head,如果temp等于NULL,就没有递归的必要了。

void Print(Node* temp)//一个参数 temp指针
{
	temp=head;//把head的值赋给temp
	if(temp==NULL)//如果链表为空,或打印到最后一个元素了,退出递归
	{
		cout<<endl;
		return;
	}
	cout<<temp->data<<" ";//输出当前值
	print(temp->next);//对下一个值调用递归
}

 逆序打印如何实现呢?顺序时,先打印值,再进行递归。那如果我们先进行递归调用,递归结束后再打印值,运行后,我们就得到了链表逆序打印的结果。这样不会修改代码的其他内容,仅仅是逆序打印。过程就是,当我们调用第一个值的时候,我们会一直递归直到最后一个值,地址为NULL,就会返回,退出递归。这时候,我们就开始打印了,从最后一个数值开始。

递归在内存中如何执行呢?分配给程序执行的内存有两部分:栈和堆。 函数的所有详细信息(包括执行信息和局部变量),malloc函数或C++中的new函数分配的所有内存,都存储在栈中。函数每次调用都会分配对应的栈帧,即使调用函数本身,我们也需不停执行栈帧。函数返回后,这个函数完成,我们将逐渐释放栈帧。

我们需要知道,对于链表顺序打印时,迭代要比递归更有效率,因为递归进行很多次调用函数,隐式使用了内存,而对于逆序打印,递归是可以的。


五、反转链表

 1)迭代方法

#include <iostream>
using namespace std;
struct Node
{
	int data;
	Node* next;
}; 
Node* head;
void Insert(int data)
{
	Node* temp=new Node();
	temp->data=data;
	temp->next=NULL;
	if(head==NULL)
	{
		head=temp;
		return; 
	}
	Node* temp1=head;
	while(temp1->next!=NULL)
	{
		temp1=temp1->next;
	}
	temp1->next=temp;
}
void Print()
{
	Node* temp=head;
	while(temp!=NULL)
	{
		cout<<" "<<temp->data;
		temp=temp->next;
	}
	cout<<endl;
}
Node* Reverse(Node * head)
{
	Node *prev=NULL,*temp=head,*next;
	while(temp!=NULL)
	{
		next=temp->next;
		temp->next=prev;
		prev=temp;
		temp=next;
	}
	head=prev;
	return head;
}
int main()
{
	head=NULL;
	Insert(2);
	Insert(4);
	Print();
	head=Reverse(head);
	Print();
	
}

 2)递归方法

void Reverse(Node* temp)
{
	if(temp->next==NULL)
	{
		head=temp;
		return;
	}
	Node* p=temp->next;
	p->next=temp;//p->next->next=p与前两行是一样的意思 
	temp->next=NULL;
}

六、双向链表

在双向链表中,每个节点将具有两个链接,一个链接下一个节点,另一个链接上一个节点

双向链表的优点和用处是什么?可以进行反向查询,仅需一个指针,可以找到上一个或下一个。缺点是,我们会为上一个节点指针使用额外的内存,重置链接时,也要重设比单链表更多的链接。

struct Node
{
	int data;
	Node* next;
	Node* prev;
};
Node* head;//全局变量

创建新节点并插入

//新节点 
Node* GetNewNode(int x)
{
	Node* newNode=new Node();//类型是Node* 
	newNode->data=x;
	newNode->next=NULL; 
	newNode->prev=NULL;
	return newNode;
} 
//或者写成
Node* GetNewNode(int x)
{
	Node newNode; //类型是Node 
	newNode.data=x;
	newNode.next=NULL;
	newNode.prev=NULL;
	return &newNode;//返回地址 
}
//插入
void InsertAtHead(int x)
{
	Node* newNode=GetNewNode(x);
	if(head==NULL)
	{
		head=newNode;
		return;
	}
	head->prev=newNode;
	newNode->next=head;//把head和newNode建立双向链接 
	head=newNode;//最后修改一下head 
}

 打印

顺序打印和单链表是一样的,逆序打印就不需要递归或迭代了,利用链接的反向遍历就可以

void ReversePrint()
{
	Node* temp=head;
	if(temp==NULL)
	{return;}
	while(temp->next!=NULL)
	{
		temp=temp->next;//先顺序遍历到最后一个节点 
	}
	while(temp!=NULL)
	{
		cout<<" "<<temp->data;//打印数据 
		temp=temp->prev;//反向遍历 
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值