常用排序算法


前言

介绍几种常用的排序算法,自己写了部分代码

冒泡排序

介绍

冒泡排序是比较基础的排序算法之一,其思想是相邻的元素两两比较,较大的数下沉,较小的数冒起来,这样一趟比较下来,最大(小)值就会排列在一端。整个过程如同气泡冒起,因此被称作冒泡排序。
冒泡排序的步骤:
1>比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2>每趟从第一对相邻元素开始,对每一对相邻元素作同样的工作,直到最后一对。
3>针对所有的元素重复以上的步骤,除了已排序过的元素(每趟排序后的最后一个元素),直到没有任何一对数字需要比较。

在这里插入图片描述

具体代码如下:

//冒泡排序
void Bubble_Sort(vector<int>& nums, int len) {
	for (int i = 0; i < len; ++i) {
		for (int j = 1; j < len - i; ++j) {
			if (nums[j] < nums[j - 1]) {
				swap(nums[j], nums[j - 1]);
			}
		}
		Read_Vec(nums);
	}
}
int main(){
	vector<int> nums = { 8,13,436,7,54,35,32,65,123,54,12};
	Bubble_Sort(nums, nums.size());
}

可以看一下执行过程,每次循环之后用Read_Vec()函数进行打印输出:
在这里插入图片描述

优化

可以加入一个bool类型的判断标记,进行优化,如果已经有序,不需要进行循环:

//冒泡排序
void Bubble_Sort(vector<int>& nums, int len) {
	for (int i = 0; i < len; ++i) {
		for (int j = 1; j < len - i; ++j) {
		    bool flag = true;
			if (nums[j] < nums[j - 1]) {
				swap(nums[j], nums[j - 1]);
				flag = false;
			}
		}
		Read_Vec(nums);
		if(flag){
		    break;
		}
	}
}
int main(){
	vector<int> nums = { 8,13,436,7,54,35,32,65,123,54,12};
	Bubble_Sort(nums, nums.size());
}

选择排序

介绍

从第一个元素开始跟剩余的所有元素比较,找出剩余元素中最小(最大)的元素位置,然后再交换开始元素位置与最小(最大)元素位置的值。(不断地选择剩余元素中的最小者)
选择排序的步骤:
1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始(结尾)位置。
2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到未排序序列的起始(结尾)位置。
3.重复第二步,直到排序完毕。

在这里插入图片描述

void Select_Sort(vector<int>& nums, int len) {
	int mid;
	for (int i = 0; i < len - 1; ++i) {
		mid = i;
		for (int j = i + 1; j < len; ++j) {
			if (nums[j] < nums[mid]) {
				mid = j;
			}
		}
		swap(nums[mid], nums[i]);
		Read_Vec(nums);
	}
}
int main() {
	vector<int> nums = { 8,13,436,7,54,35,32,65,123,54,12};
	Select_Sort(nums, nums.size());
}

同样看一下执行过程:
在这里插入图片描述

优化

可以同时一次找出最大值和最小值,这样就可以减少一半的遍历次数,

void Select_Sort(vector<int>& nums, int len) {
	int Min, Max;
	int right = len - 1;
	for (int left = 0; left <= right; ++left, --right) {
		Min = left, Max = left;
		for (int j = left + 1; j <= right; ++j) {
			if (nums[j] < nums[Min]) {
				Min = j;
			}
			if (nums[j] > nums[Max]) {
				Max = j;
			}
		}
		if (Min == right && Max == left) {
			swap(nums[Min], nums[Max]);  //这时只需要交换一次,交换两次则没变化
		}
		else {
			swap(nums[left], nums[Min]);
			swap(nums[right], nums[Max]);
		
		}
		Read_Vec(nums);
	}
}

插入排序

将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据。算法适用于少量数据的排序。
类比打扑克牌,我们已经整理好手中的牌,每拿到一张新的牌,我们会把它插到适当的位置,使得整个序列仍然有序。
在这里插入图片描述
代码实现:

//插入排序
void Insert_Sort(vector<int>& nums, int len) {
	for (int i = 1; i < len; ++i) {
		for (int j = i; j > 0 && nums[j - 1] > nums[j]; --j)
			swap(nums[j - 1], nums[j]);
		Read_Vec(nums);
	}
}
int main() {
	vector<int> nums = { 8,13,436,7,54,35,32,65,123,54,12};
	Insert_Sort(nums, nums.size());
}

具体过程:
在这里插入图片描述

希尔排序

希尔排序的实质就是分组插入排序,该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。
该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
插入排序每次调整的步数为1,Shell排序步数则是从一个大的数字逐渐减小,调高效率:
在这里插入图片描述
具体代码:

//希尔排序
void Shell_Sort(vector<int>& nums, int len) {
	int step = len / 2;
	while (step > 0) {
		for (int i = step; i < len; ++i) {
			for (int j = i; j - step >= 0 && nums[j - step] > nums[j]; j -= step)
				swap(nums[j], nums[j - step]);
			Read_Vec(nums);
		}
		step /= 2;
	}
}

int main() {
	vector<int> nums = { 8,13,436,7,54,35,32,65,123,54,12};
	Shell_Sort(nums, nums.size());
}

执行过程:
在这里插入图片描述

堆排序

堆排序视频讲解:

堆排序(heapsort)


讲解的十分细致到位,一看就懂!
代码实现:

void Heapify(vector<int>& nums, int n, int i) {
	if (i >= n)
		return;
	int c1 = 2 * i + 1;  //左孩子下标
	int c2 = 2 * i + 2;  //右孩子下标
	int mid = i;
	if (c1 < n && nums[c1] > nums[mid]) {
		mid = c1;
	}
	if (c2 < n && nums[c2] > nums[mid]) {
		mid = c2;
	}
	if (mid != i) {
		swap(nums[mid], nums[i]);
		Heapify(nums, n, mid);
	}
}
void Build_Heap(vector<int>& nums, int len) {
	int parent = (len - 1) / 2;
	for (int i = parent - 1; i >= 0; --i) {
		Heapify(nums, len, i);
	}
}
void Heap_Sort(vector<int>& nums, int len) {
	Build_Heap(nums, len);
	cout << "建立完成大顶堆后的数字顺序:" << endl;
	Read_Vec(nums);
	cout << endl << "调整顺序:" << endl;
	for (int i = len - 1; i >= 0; --i) {
		swap(nums[i], nums[0]);
		Heapify(nums, i, 0);
		Read_Vec(nums);
	}
}

测试相同的数据,观察结果:
在这里插入图片描述

快速排序

快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。
快速排序使用分治的思想,从待排序序列中选取一个记录的关键字为key,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字不大于key,另一部分记录的关键字不小于key,之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

快速排序算法的基本步骤为(从小到大):

1.选择关键字:从待排序序列中,按照某种方式选出一个元素作为 key 作为关键字(也叫基准)。
2.置 key 分割序列:通过某种方式将关键字置于一个特殊的位置,把序列分成两个子序列。此时,在关键字 key 左侧的元素小于或等于 key,右侧的元素大于 key(这个过程称为一趟快速排序)。
3.递归上述过程,直至排序结束

在这里插入图片描述
代码实现:

//快速排序
void Quick_Sort(vector<int>& nums, int left, int right) {
	if (left >= right)
		return;
	int base = nums[left];
	int i = left, j = right;
	while (true) {
		while (nums[j] >= base && i < j)
			--j;
		while (nums[i] <= base && i < j)
			++i;
		if (i >= j)
			break;
		swap(nums[i], nums[j]);
	}
	swap(nums[left], nums[i]);
	Quick_Sort(nums, left, j - 1);
	Quick_Sort(nums, j + 1, right);
}

测试过程:
在这里插入图片描述

基数排序

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
基数排序法是属于稳定性的排序,基数排序法是效率高的稳定性排序法。
基数排序(Radix Sort)是桶排序的扩展,它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。
在这里插入图片描述
具体实现如下:

//基数排序
void Radix_Sort(vector<int>& nums, int len) {
	int MaxData = INT_MIN;
	for (int i = 0; i < len; ++i) {
		MaxData = max(MaxData, nums[i]);
	}
	int radix = 1;
	vector<int> temp(len, 0);
	while (MaxData / radix > 0) {
		int bucket[10] = {0};
		for (int i = 0; i < len; ++i) {
			bucket[nums[i] / radix % 10]++;
		}
		for (int i = 1; i < 10; ++i)
			bucket[i] += bucket[i - 1];
		for (int i = len - 1; i >= 0; --i) {
			temp[bucket[nums[i] / radix % 10] - 1] = nums[i];
			bucket[nums[i] / radix % 10]--;
		}
		for (int i = 0; i < len; ++i) {
			nums[i] = temp[i];
		}
		radix *= 10;
		Read_Vec(nums);
	}
}

测试同上的数据,执行过程如下:
在这里插入图片描述

归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

简单的来说,归并排序主要分为三步,一是对数组的划分,二是对数组的排序,三是对数组的合并。划分的大小是可以随自己的想法而设置,但是一般都是以2为单位,这样最小的一组的排序就比较方便。
在这里插入图片描述
具体代码实现:

void Merge(vector<int>& nums, vector<int>& temp,
	int left_begin, int right_begin, int right_end) {
	int left_end = right_begin - 1;
	int len = right_end - left_begin + 1;
	int i = left_begin;
	while (left_begin <= left_end && right_begin <= right_end) {
		if (nums[left_begin] <= nums[right_begin]) {
			temp[i++] = nums[left_begin++];
		}
		else {
			temp[i++] = nums[right_begin++];
		}
	}
	while (left_begin <= left_end)
		temp[i++] = nums[left_begin++];
	while (right_begin <= right_end)
		temp[i++] = nums[right_begin++];
	for (int i = 0; i < len; ++i, right_end--) {
		nums[right_end] = temp[right_end];
	}
}
void M_Sort(vector<int>& nums, vector<int>& temp, int left, int right) {
	int mid;
	if (left < right) {
		mid = (right - left) / 2 + left;
		M_Sort(nums, temp, left, mid);
		M_Sort(nums, temp, mid + 1, right);
		Merge(nums, temp, left, mid + 1, right);
		Read_Vec(nums);
	}
}
void Merge_Sort(vector<int>& nums, int len) {
	vector<int> temp(nums.size(), 0);
	M_Sort(nums, temp, 0, len - 1);
}

测试过程:
在这里插入图片描述

总结

常用排序算法小结,各个时间复杂度,空间复杂度如图,在不同的场景选择最合适的排序方式。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值