单向链表的实现和排序算法

单向链表的两种实现

第一种方法:
此方法较为典型,便于理解。添加、插入、删除的复杂度都是O(N).

//单向链表(一) 
#include<iostream>
using namespace std;
struct Node//节点结构体 
{
	int Data;
	Node* next;
};
class Link//链表类 
{
	public:
		int length;
		Node *head;//头节点 
		Link()//构造方法 
		{
			length=0;
			head=NULL;
		}
		void add(int a)//目的:向链表末尾插入一个节点,该节点data值为a
						/*实现方法:通过头结点遍历链表,找到尾节点,
						new一个节点对象,把尾节点的next指针指向这个新对象 */
		{
			++length;
			if(head==NULL) 
			{
				head=new Node;
				head->Data=a;
				head->next=NULL;
			}
			else
			{
				Node *temp=head;
				while(temp->next!=NULL)
				{
					temp=temp->next;
				}
				temp->next=new Node;//申请空间
				temp=temp->next;
				temp->Data=a;
				temp->next=NULL;
			}
		}
		void insert(int a,int index){
		//目的:向第index个元素后(不是下标) 插入一个新节点,其值为a 
		/*实现方法:通过头节点搜索链表,直到第找到第index个节点,
		new一个节点对象,将第index节点的next指针指向该对象 */
			--index;
			if(head==NULL||index>length){
				add(a);
				++length;
				return;
			}
			else{
				++length;
				int count=0;
				Node* temp=head;
				while(count<index){
					++count;
					temp=temp->next;
				}
				Node *plus=new Node;
				plus->Data=a;
				plus->next=temp->next;
				temp->next=plus;
			}
		}
		void remove(int index){
		//目的:删除一个节点
		//实现方法:找到第index个节点的上一个节点,把它的next指针指向 第index节点的下一个节点。 
			if(index>length){
				cout<<"没了\n"; 
				return;
			}
			int count=0;
			Node* temp=head;
			while(count<index-1){
				++count;
				temp=temp->next;
			}
			Node *del=temp->next->next;
			temp->next=del;
			--length;
		}
};
int main()
{
	Link list;
	for(int i=1; i<=9; ++i)
	{
		list.add(i);
	}
	cout<<"节点数目--"<<list.length<<'\n'; 
	Node *temp=list.head;
	while(temp!=NULL){
		cout<<temp->Data<<'\n';
		temp=temp->next;
	}
	cout<<"=================================\n";
	list.insert(100,3); 
	temp=list.head;
	while(temp!=NULL){
		cout<<temp->Data<<'\n';
		temp=temp->next;
	}
	cout<<"=================================\n";
	list.remove(3);
	temp=list.head;
	while(temp!=NULL){
		cout<<temp->Data<<'\n';
		temp=temp->next;
	}
}

第二种方法
下面这种方法相对于第一种,增加了一个尾节点。利用这个尾节点,我将add部分做了一些优化使得向末尾插入时能实现O(1)的复杂度。事实上,借助这种思想还有其他可以实现的优化,这里只是提一下,其他的希望读者能自己动手实现。

//单向链表(一) 
#include<iostream>
using namespace std;
struct Node//节点结构体 
{
	int Data;
	Node* next;
};
class Link//链表类 
{
	public:
		int length;
		Node *head;//头节点  
		Node *last;//尾节点 
		Link()//构造方法 
		{
			length=0;
			head=NULL;
			last=head;
		}
		void add(int a)//目的:向链表末尾插入一个节点,该节点data值为a
						/*实现方法:直接向尾节点后加一个新的节点,复杂度为O(1) */
		{
			++length;
			if(head==NULL) 
			{
				head=new Node;
				head->Data=a;
				head->next=NULL;
				last=head;//链表中只有一个节点,既是头也是尾 
			}
			else
			{
				Node *temp=new Node;//新建节点,存a 
				temp->Data=a;
				temp->next=NULL;
				last->next=temp;	
				last=temp;
			}
		}
		void insert(int a,int index){
		//目的:向第index个元素后(不是下标) 插入一个新节点,其值为a 
		/*实现方法:通过头节点搜索链表,直到第找到第index个节点,
		new一个节点对象,将第index节点的next指针指向该对象 */
			--index;
			if(head==NULL||index>length){
				add(a);
				++length;
				return;
			}
			else{
				++length;
				int count=0;
				Node* temp=head;
				while(count<index){
					++count;
					temp=temp->next;
				}
				Node *plus=new Node;
				plus->Data=a;
				plus->next=temp->next;
				temp->next=plus;
			}
		}
		void remove(int index){
		//目的:删除一个节点
		//实现方法:找到第index个节点的上一个节点,把它的next指针指向 第index节点的下一个节点。 
			if(index>length){
				cout<<"没了\n"; 
				return;
			}
			int count=0;
			Node* temp=head;
			while(count<index-1){
				++count;
				temp=temp->next;
			}
			Node *del=temp->next->next;
			temp->next=del;
			--length;
		}
};
int main()
{
	Link list;
	for(int i=1; i<=9; ++i)
	{
		list.add(i);
	}
	cout<<"节点数目--"<<list.length<<'\n'; 
	Node *temp=list.head;
	while(temp!=NULL){
		cout<<temp->Data<<'\n';
		temp=temp->next;
	}
	cout<<"=================================\n";
	list.insert(100,3); 
	temp=list.head;
	while(temp!=NULL){
		cout<<temp->Data<<'\n';
		temp=temp->next;
	}
	cout<<"=================================\n";
	list.remove(3);
	temp=list.head;
	while(temp!=NULL){
		cout<<temp->Data<<'\n';
		temp=temp->next;
	}
}

后面会继续写一下双向链表和其他的一些有趣的算法,既作为自身基础知识的巩固,也起到小小的科普作用

单向链表的排序算法

众所周知,排序算法多种多样,但常写的快排和归并一般都是针对数组的版本。那对于链表应该怎样排序呢?其实道理是一样的,只是实现起来有些细节上的不同。
先看看快排。

快速排序

  • 快排很重要的一点是找基准点,在链表中很容易实现,直接取头节点就好。
  • 在数组中,进行一轮基准定位排序是很方便的,直接从最右端开始 --,再从最左端开始 ++,通过下标遍历。可是对于单向链表,没有办法通过子节点去找父节点,也就是说不知道如何分割链表。回顾一下上文,我们只知道左子链的左端和右子链的右端在哪,换言之,所要求的是左子链的右端和右子链的左端,中间夹着的是基准点。
  • 这时就需要大家发动一下想象力,构造两个辅助指针p1和p2。其中p1作为左子链的尾节点(右端),p2就是右子链的头节点(左端)

具体操作: 在搜索的时候只动p2,当p2找到比基准值小的节点时,将p1向右移动一位(相当于给左子链扩容),然后交换p1和p2。当搜索到末尾时,即p2=NULL,和数组快排一样,需要把head和p1换位。

代码如下:

void swap(Node* p1,Node* p2){
	Node t;
	t.Data=p1->Data;
	p1->Data=p2->Data;
	p2->Data=t.Data;
}
void qsort(Node* head,Node* tail){
	if(head==NULL||head==tail){
		return;
	}
	
	int datum=head->Data;//基准点数值 
	Node*p1=head;
	Node*p2=head->next;
	
	while(p2!=tail){
		if(p2->Data<datum){//找到了比基准点小的节点,应放到基准点左边
			p1=p1->next;//先扩容 
			swap(p1,p2);
		}
		p2=p2->next;
	}
	swap(head,p1);//把头结点移到基准点位置 
	qsort(head,p1);//对左链排序
	qsort(p1->next,tail);//对右链排序 
}

这里还给大家介绍一个数值交换的小技巧,通过异或位运算实现。
伪代码是:a=a ^ b ; b=a ^ b ; a=a^b;
还可以简化成:a^ =b^ =a^ =b;、
但要清楚的是,这种位运算对于提高性能用处不大,也降低了可读性。但能让你看起来很专业

归并排序

相比于数组,链表在插入和删除上的表现十分优异,很适于归并。
在讲解链表归并之前,先回忆一下数组归并。

这篇博客讲得很好 > > > https://www.cnblogs.com/chengxiao/p/6194356.html < < <

归并算法是分治思想的鲜明体现,先从终点开始,用递归把数组分成一个个数字并排序(单独的数字一定有序),再开始合并,并起来时是把小的放左边,大的放右边,如果有一个数组空了,就把另一个全部放进来。
那么现在的问题分成了两个:

  1. 如何从中点切开链表
  2. 如何合并链表

第一个问题需要开一下脑洞,使用一个叫做“快慢指针”的方法。

首先两个指针都指向head,快指针一次走两格,慢指针一次走一格,那么当快指针指到最后一个节点时,即为NULL时,由于慢指针速度只有快指针的一半,慢指针刚好走到中点。

借助这样的特性就可以定位链表的中点了

void mergeLink(Node* first,Node* r_mid, Node* last){
	Link temp;
	Node* i=first, *j=r_mid;
	while(i!=NULL&&j!=NULL){
		if(i->Data<j->Data){
			temp.add(i->Data);
			i=i->next;
		}
		else{
			temp.add(j->Data);
			j=j->next;
		}
	}
	while(i!=NULL){
		temp.add(i->Data);
		i=i->next;
	}
	i=r_mid;
	while(j!=NULL){
		temp.add(j->Data);
		j=j->next;
	}
	*first=*temp.head;//让first直接指向拼好的链表 
} 
void mergeSort(Node* head){
	if(!head||!head->next){
		return;
	}
	Node* slow=head,*fast=head->next;
	//如果让fast指向head,那么最后slow本身就是中点,
	//在删除slow的时候,只能使slow自身变为NULL,而它的上一位next的指向并不为空 
	while(fast&&fast->next){
		fast=fast->next->next;
		slow=slow->next;
	}	
	Node* right=slow->next;
	mergeSort(right);//右链排序 
	slow->next=NULL;//切断 
	Node* left=head;
	mergeSort(left);//左链排序 
	mergeLink(left,right,NULL);//合并 
		//   左起点 右起点 终点 
} 

关于快慢指针 其实还有很多别的用途,推荐大家看下这篇博客

快慢指针应用总结 > > > https://blog.csdn.net/qq_21815981/article/details/79833976 < < <

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值