C++链表的排序实现

这篇具有很好参考价值的文章主要介绍了C++链表的排序实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

对于链表的排序算法,除了希尔排序之外,且堆排序不建议,其他排序方法都是支持的,如下:

冒泡排序,选择排序,插入排序,归并排序,快速排序,计数排序,桶排序和基数排序。

下面废话不多说,直接开始排序

定义一个链表的结构

这里我定义一个简单的链表节点(头节点不包含数据),如下所示

typedef struct Node{
	int data;
	Node* next;
	Node():data(0),next(NULL){};
	Node(int x):data(x),next(NULL){};
}*Head;

//添加节点
void AddNode(Head head,Node* node){
	node->next = head->next;
	head->next = node;
}

//输出
#define for_each(pow, head) \
	for(pow = head->next; pow != NULL; pow = pow->next)

这里我们首先进行初始化,以及添加一些节点。

int main(void){

	Head list_head = new Node();  //头节点

	Node* node = NULL;
	node = new Node(3);
	AddNode(list_head, node);
	node = new Node(2);
	AddNode(list_head, node);
	node = new Node(8);
	AddNode(list_head, node);
	node = new Node(4);
	AddNode(list_head, node);
	node = new Node(5);
	AddNode(list_head, node);

	Node* pow=NULL;
	for_each(pow, list_head){
		cout<<pow->data<<endl;
	}


	system("pause");
	return 0;
}

经过测试,对于链表初始化,添加节点,以及遍历显示都正常,现在开始尝试我们的排序算法。

冒泡排序
  • 算法思想:每次比较相邻元素之间的大小,使值小的逐步从后面移动到前面,值大的从前面移到后面。
  • 时间复杂度:
    • 最好时间复杂度:O(n)
    • 最坏时间复杂度:O(n2)
  • 排序稳定性:由于元素交换是在相邻元素之间进行的,不会改变值相同元素的相对位置,因此,冒泡排序法是一种 稳定排序算法
void BubbleSort(Head &head){
	Node* i = head->next;
	Node * tail = NULL;
	for(; i->next != NULL; i = i->next){
		Node* j = head->next;
		for(; j->next != tail; j = j->next){
			if(j->data > j->next->data){
				int temp = j->data;
				j->data = j->next->data;
				j->next->data = temp;
			}
		}
		tail = j;  //每次向前移动一位
	}
}

冒泡排序的链表实现和数组是一样的,只不过需要三个指针进行两个for循环,最后一个指针tail用于指向j这个指针的末尾,每次循环,则-1。

选择排序
  • 算法思想:每次从未排序的元素中找到最小的值,并与未排序最前面的元素交换位置。
  • 时间复杂度:因为排序比较次数与序列的原始状态无关,总是O(n2)
  • 排序稳定性:因为交换不是相邻两元素,可能会改变值相同元素的前后位置,故是一种不稳定的排序算法

C++实现

void SectionSort(Head &head){
	Node* i = head->next;
	for(; i->next != NULL; i = i->next){
		Node* j = head->next;
		Node* min_node=i->next;
		for(j = i; j != NULL; j = j->next){
			if(min_node->data > j->data){
				min_node = j;
			}
		}
		if(i != min_node){
			Swap(i,min_node);
		}
	}
}

这次只需要两个指针,每次从i开始遍历到结尾,找出最小的数据节点,将其与i的位置进行调换,然后“i++”。

插入排序
  • 算法思想:每次将无序列表中的元素插入到有序列表中。
  • 时间复杂度:
    • 最佳时间复杂度:O(n)
    • 最差时间复杂度:O(n2)
    • 平均时间复杂度:O(n2)
  • 排序稳定性:稳定排序算法

C++实现

void InsertSort(Head &head){
	Node* i = head->next;
	if(i == NULL) return;
	else if(i->next == NULL) return;

	Node* sort_list=head->next;  //第一个节点
	i = i->next;  
	while(i){ //第二个节点开始
		if(sort_list->data <= i->data){
			sort_list = sort_list->next; //有序的则移动
		}
		else{
			Node* p1 = head; //从有序链表开始遍历
			while(p1->next->data <= i->data){
				p1 = p1->next;
			}
			// 插入操作
			sort_list->next = i->next;
			i->next = p1->next;
			p1->next = i;
		}
		i = sort_list->next;
	}
}

插入排序的实现,相比之数组,要复杂许多。数组的插入排序,可以在插入数据与有序数组比较的过程中完成位移覆盖,这取决于数组的删除特性。而链表则不行,实现过程由三个指针。

sort_list指针用来维护有序列表的尾部。i指针用来表示无序区域的值,而这里我们的头节点作为插入排序比较的指针。

  • 比较有序列表尾部与无序列表的首部的值,如果无序列表首部大,则有序列表的sort_list指针向前移动。
  • 如果无序列表首部比有序列表的尾部要小,那么就需要进行插入操作。这里p1指针从头部开始遍历,直到找到i应该插入的地方。
  • 然后进行三个指针指向的操作,完成插入步骤。
归并排序
  • 算法思想:不断的分割数据,直到数据不能在被分割,在通过归并的思想,将数据合并起来。
  • 时间复杂度:
    • 时间复杂度:O(n×log2⁡n)。
    • 空间复杂度:O(1)。
  • 排序稳定性:由于元素交换是在相邻元素之间进行的,不会改变值相同元素的相对位置,因此,冒泡排序法是一种 稳定排序算法

C++实例

list_head->next = MergeSort(list_head->next);
for_each(pow, list_head) cout<<pow->data<<"\t"; cout<<endl;

这段是测试代码,因为归并的时候,会将数据进行分割,不适合头节点无数据,这里直接使用list_head->next作为参数传入数据。

Head MergeSort(Head head){

	if(NULL == head || NULL == head->next) return head;

	// 快慢指针找到中心链节点
	Node* slow=head;
	Node* fast=head->next;
	while(fast && fast->next){
		slow = slow->next;
		fast = fast->next->next;
	}

	// 得到两个链表
	Node* left_head = head;
	Node* right_head = slow->next;
	slow->next = NULL;

	// 归并操作
	return Merge(MergeSort(left_head), MergeSort(right_head));
}

归并排序操作如下:

  1. 判断第一个节点和第二个节点是否有数据,没有则直接返回。
  2. 使用快慢指针找到中间的节点
  3. 将其划分为左链表和右链表
  4. 我们将左边和右边的链表通过MergeSort排序好,之后在进行归并操作。

因为采用的递归的思想,你可以理解为Merge函数中的左边和右边已经通过MergeSort排序好了。

然后是归并函数的实现

Head Merge(Node* left, Node* right){

	Head head = new Node();
	Node* cur = head;
	// 分离双指针进行数组合并
	while(left && right){
		if( left->data <= right->data){
			cur->next = left;
			left = left->next;
		}else{
			cur->next = right;
			right = right->next;
		}
		cur = cur->next;
	}

	// 清理剩余的链表
	if(left) cur->next = left;
	else if(right) cur->next = right;

	return head->next;
}

这里主要采用了分离双指针的操作,较为简单。

  1. 将左边和右边的数据通过两个指针索引进行比较,因为是排好顺序的,所以只需要不断移动指针即可。
  2. 最后将还没有使用完的剩余链表直接添加,就结束了。
链表快速排序
  • 算法思想:随机取序列中的一个值,并将序列以此值为界限,一分为二,并递归进行,直到整个序列有序
  • 时间复杂度:
    • 时间复杂度:O(n×log2⁡n)。
    • 空间复杂度:O(1)。
  • 排序稳定性:不稳定排序算法

C++实现

Head QuickSort(Node* head){
	if(NULL == head->next || NULL == head->next->next) return head;
	head->next = Quick(head->next, NULL);
	return head;
}

也是同样的,因为头节点没有数据,所以这里先将头节点提取出来之后,在进行排序。

Node* Quick(Node* left, Node* right){
	// 基线条件
	if(left == right || left->next == right) return left;

	//left链表的值被更新,且得到分割点地址
	Node* pi = Partition(left, right);

	Quick(left, pi);  
	Quick(pi->next, right);
	return left;
}

同样,这里采用递归的方式进行排序算法。

  1. 首先是基线条件,当从左到右,只有0或者1个节点的时候,返回该分段的起始地址left。
  2. Partition函数的作用是选取头节点作为基准值,然后对链表的值进行分割。
    • 方式和数组有异曲同工之妙,这里只对数据进行了修改,没有对节点进行修改。
  3. 然后递归对left到pi再次进行快排。

这里巧妙之处在于,虽然链表导致数据存储的物理位置不是连续的,但是链表的地址可以看成是连续的,这里我们通过链表的地址,来分割左边和右边。

计数排序
  • 算法思想:使用一个范围涵盖所有数据的额外数组统计原数组中元素出现的个数,在排到正确的顺序
  • 时间复杂度:O(n+k),其中 k 代表待排序链表中所有元素的值域。
  • 空间复杂度:O(k)。
  • 排序稳定性:稳定排序算法

C++实现

Head CountingSort(Head head){
	if(NULL == head->next || NULL == head->next->next) return head;
	
	// 获取最大值和最小值
	int list_max = INT_MIN,list_min = INT_MAX;
	Node* pow = head->next;
	while(pow){
		if(pow->data > list_max) list_max = pow->data;
		if(pow->data < list_min) list_min = pow->data;
		pow = pow->next;
	}
	
	// 初始化数组
	const int size = list_max-list_min+1;
	int counts[size] = {0};

	// 计数
	pow = head->next;
	while(pow){
		counts[pow->data - list_min] += 1;
		pow = pow->next;
	}

	pow = head;
	for(int i=0; i<size; i++){
		
		while(counts[i]){
			pow->next->data = i+list_min;
			counts[i] -= 1;
			pow = pow->next;
		}
	}

	return head;
}

计数排序原理比较简单,因为引入了额外的空间,操作更加的简便。

  1. 判断链表中节点的数量
  2. 找出最小值和最大值,生成counts数组
  3. 对链表中的数进行计数
  4. 将计数的结果在返回给链表。
桶排序
  • 算法思想:
  • 时间复杂度:
    • 时间复杂度:O(n)。
    • 空间复杂度:O(n+m)。m 为桶的个数。
  • 空间复杂度:O(n+m)
  • 排序稳定性:稳定排序算法

C++实现

void Insertion(vector<Head> &buckets,int index, int val){
	if(!buckets[index]){
		buckets[index] = new Node(val);
		return;
	}

	Node* node = new Node(val);
	node->next = buckets[index];
	buckets[index] = node;
}

插入函数的作用就是,将链表中的所有数据分别放到各个桶里面。

Head bucketSort(Head head, int bucket_size=20){
	if(NULL == head->next || NULL == head->next->next) return head;

	//找出最大值和最小值
	int list_max = INT_MIN,list_min = INT_MAX;
	Node* pow = head->next;
	while(pow){
		if(pow->data > list_max) list_max = pow->data;
		if(pow->data < list_min) list_min = pow->data;
		pow = pow->next;
	}

	int bucket_count = (list_max-list_min) / bucket_size + 1;
	vector<Head> buckets(bucket_count);
	cout << "bucket_count:" << bucket_count <<endl;

	// 链表数据分组
	Node* cur = head->next;
	while(cur){
		int index = (cur->data - list_min) / bucket_size;
		Insertion(buckets, index, cur->data);
		cur = cur->next;
	}

	// 桶内采用归并排序
	cur = head;
	for(int i=0; i<buckets.size(); i++){
		Head list= MergeSort(buckets[i]);
		while(list){
			cur->next = list;
			cur = cur->next;
			list = list->next;
		}
	}

	return head;
}

桶排序其核心思想就是分桶,有点分而治之的思想。然后在对桶中的数据进行各种排序方法。

  1. 找出最大值,最小值,然后根据bucket_size来进行分桶
  2. 将链表中的数据分桶装入其中
  3. 每个桶执行各自的排序算法,最后在进行合并
基数排序
  • 算法思想:将整数按位数切割成不同的数字,然后按每个位数分别比较进行排序
  • 时间复杂度:O(n*k),k表示位数
  • 空间复杂度:O(n+k)
  • 排序稳定性:稳定排序算法

C++实现

int Pow(int a, int i){
	int res = 1;
	while(i){
		res *= a;
		i--;
	}
	return res;
}

简单实现了一个幂次方函数,不错也可以直接调cmath库中的内容

Head RadixSort(Head head){
	int size = 0;
	Node* cur = head->next;
	while(cur){
		int val_len = std::to_string(cur->data).size();
		size = val_len>size?val_len:size;
		cur=cur->next;
	}

	for(int i=0; i<size; i++){
		vector<vector<int>> buckets(10);
		cur = head->next;
		while(cur){
			buckets[cur->data/Pow(10,i)%10].push_back(cur->data);
			cur = cur->next;
		}

		// 生成新表
		cur = head;
		for(int i=0; i<buckets.size(); i++){
			for(int j=0; j<buckets[i].size(); j++){
				cur->next = new Node(buckets[i][j]);
				cur = cur->next;
			}
		}
	}
	return head;
}
  1. 找出最大值的位数。
  2. 遍历每一位,并将数据分桶装载。
  3. 将分桶的数据,按照顺序返回给head。

总结

这9中排序在实现难度上比之前的数组还困难许多,不过同时也收获许多。文章来源地址https://www.toymoban.com/news/detail-435327.html

到了这里,关于C++链表的排序实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【链表】还不会用C++实现链表?一文教会你各种链表的实现

    本文将用C++语言来实现数据结构中的无头单链表,带头循环链表,以及带头循环双向链表等链表结构(带头单链表与后两种链表的结构相似,实现起来比后两种更简单,读者阅读完本文即可自行实现) 无头单链表在头插时需要改变头指针的位置,具体代码实现如下: 带头意

    2024年02月08日
    浏览(32)
  • 编程导航算法通关村第 1 关|青铜 - C++是如何构造出链表的

             在C++中,链表是由一系列节点构成的,每个节点包含一个值和一个指向下一个节点的指针。         我们可以用结构体定义出一个节点:               在定义完后,我们将链表进行初始化,并插入5条数据:   接着我们在主函数中进行测试,看看是否能成

    2024年02月13日
    浏览(30)
  • 关于链表的建立与操作(c++实现)

    目录 1. 链表的定义 2.单链表的基本操作 3. 循环链表及其操作 4.双向链表及其操作 5.用数组模拟链表 因为线性表是静态线性的存储结构,所以为了方便动态地对数据进行处理,我们引入链表这一数据结构。 因为链表是动态的存储结构,所以存储在其中的数据地址不一定是连续

    2024年02月08日
    浏览(21)
  • Java 算法篇-链表的经典算法:判断回文链表、判断环链表与寻找环入口节点(“龟兔赛跑“算法实现)

    🔥博客主页: 【 小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍       文章目录         1.0 链表的创建         2.0 判断回文链表说明         2.1 快慢指针方法         2.2 使用递归方式实现反转链表方法         2.3 实现判断回文链表 - 使用快慢指针与反转链表

    2024年02月05日
    浏览(51)
  • 2、有序链表的维护【问题描述】编写一个程序,根据从标准输入接收的指令来维护和操作排序的链表(C语言、java和Python分别实现)

    【问题描述】 编写一个程序,根据从标准输入接收的指令来维护和操作排序的链表。链表是按顺 序维护的,这意味着链表中的数据在每次操作后都以递增的数字顺序存储。 请注意,在创建新节点时,需要使用malloc为它们分配空间;一旦不再需要任何已 分配的空间,就应该使

    2024年02月09日
    浏览(34)
  • 数据结构上机练习——单链表的基本操作、头文件、类定义、main函数、多种链表算法的实现,含注释

      头文件和源文件分开有很多好处:可以提高编译速度、提高代码的可维护性、提高代码的可重用性和可扩展性,同时也可以使代码结构更清晰,方便代码的管理和维护。 LinkList.h test.cpp                  (下面所有函数都默认在类中实现)   我们以

    2024年02月07日
    浏览(38)
  • Java 算法篇-链表的经典算法:有序链表去重、合并多个有序链表

    🔥博客主页: 【 小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍         文章目录          1.0 链表的说明          2.0 有序链表去重的实现方式         2.1 有序链表去重(保留重复的节点) - 使用递归来实现         2.2 有序链表去重(保留重复的节点) - 使用双指针

    2024年02月05日
    浏览(34)
  • c++中的&以及链表的基础使用

    通俗的立减 即为对一个变量起别名。(是和指针有区别的) 以下为两个示例程序: 通过代替了以往对地址的传递。从而实现了对a和b的交换。   p为a的别名,对p操作即为对a操作。故最后输出a的值为10.       

    2024年01月16日
    浏览(23)
  • 【算法篇C++实现】常见排序算法

    算法精炼 每趟从待排序的记录中选出最小的记录,顺序放在已排序的记录序列末尾,直到全部排序结束为止。 简单排序处理流程 从待排序序列中,找到最小的元素; 如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换; 从余下的 N - 1 个元素中

    2024年02月13日
    浏览(30)
  • [算法通关村] 1.2 链表的插入

    上一节我们谈到了链表的概念,以及链表的创建方法,忘记的小伙伴可以复习一下: [算法通关村] 1.1 单向链表的创建         今天我们来探究一下链表的插入,链表的插入共有 3 种可能性,分别是在链表的 头部 插入、在 中间 插入,和在链表的 尾部 插入(追加),不同

    2024年02月15日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包