冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序

1.排序的分类概述

在这里插入图片描述

排序的定义

对一序列对象根据某个关键字进行排序

术语说明

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b前面。
不稳定:如果a原本在b前面,而a=b,排序之后a可能出现在b的后面
内排序:所有排序操作都在内存中完成
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行
时间复杂度:一个算法执行所消耗的时间
空间复杂度:运行完一个程序所需内存大小

算法总结

在这里插入图片描述
参考文章:

2.冒泡排序、选择排序、插入排序

此部分较为基础,附上参考链接:

3.归并排序

归并排序的流程

在这里插入图片描述
其c++代码如下所示:

//使用递归对数组data从索引i到索引j之间的元素排序
void MergeSort(vector<int>& data, int i, int j) {
	if (j == i) return;//递归结束条件
	int mid = i + (j - i) / 2;
	MergeSort(data, i, mid);//对前半部份进行排序
	MergeSort(data, mid + 1, j);//对后半部分进行排序
	Sort(data, i, mid, j);//将两个有序数组归并成一个有序数组
}

此处Sort(data, i, mid, j)是合并两个有序数组,其合并逻辑如下:
在这里插入图片描述
Sort函数代码实现如下:

void Sort(vector<int>& data, int i, int mid, int j) {
	//一个临时空间,存放排序好的数组,最后将这个数组赋值给data[i]到data[j]
	vector<int> tmp(j - i + 1);

	int idx1 = i;//数组1索引
	int idx2 = mid + 1;//数组2索引
	int k = 0;

	//两个索引都没超出边界
	while ((idx1<=mid)&&(idx2<=j)) {
		if (data[idx1] <= data[idx2])
			tmp[k++] = data[idx1++];
		else tmp[k++] = data[idx2++];
	}

	//索引idx1超出数组1边界(数组1访问完毕)而索引idx2还未超出边界
	while ((idx1 > mid) && (idx2 <= j))
		tmp[k++] = data[idx2++];

	//索引idx2超出数组2边界(数组2访问完毕)而索引idx1还未超出边界
	while ((idx1 <= mid) && (idx2 > j))
		tmp[k++] = data[idx1++];

	//这一步对于data的索引很容易出错,需要弄清楚的是data是从左边界i开始的
	for (int m = 0; m < j - i + 1; m++)
		data[i + m] = tmp[m];
}

写成类封装(对比上面稍微改了点逻辑,保留了原始的data):

#include<iostream>
#include<vector>
using namespace std;

class Solution {
public:
	vector<int> Mergesort(vector<int>data, int right) {//right=data.size()-1
		if (right == 0)return data;
		int mid = right/ 2;
		vector<int> data1(data.begin(), data.begin() + mid + 1);
		vector<int> data2(data.begin() + mid + 1, data.end());
		data1 = this->Mergesort(data1,mid);
		data2 = this->Mergesort(data2, right - mid - 1);
		return this->Sort(data1, data2, mid + 1, right - mid);
	}
private:
	vector<int>Sort(vector<int>data1, vector<int>data2, int size1, int size2) {
		vector<int> tmp(size1 + size2);
		int idx1 = 0, idx2 = 0, k = 0;
		while (idx1 < size1 && idx2 < size2) {
			if (data1[idx1] <= data2[idx2])tmp[k++] = data1[idx1++];
			else tmp[k++] = data2[idx2++];
		}
		while (idx1 >= size1 && idx2 < size2)
			tmp[k++] = data2[idx2++];
		while (idx2 >= size2 && idx1 < size1)
			tmp[k++] = data1[idx1++];
		return tmp;
	}
};
int main() {
	Solution s;
	vector<int> vec{ 9,8,7,6,5,4,3,2,1 };
	vector<int>s1 = s.Mergesort(vec, vec.size() - 1);
	for (auto it : s1)
		cout << it << endl;
	return 0;
}

参考文章:

4.堆排序

4.1准备知识

堆的结构可以分为大根堆小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序。
所谓完全二叉树即叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。
存储一个完全二叉树,最适合使用数组,因为它相比链表不需要存储左、右子树的指针,更加节省内存空间,通过数组索引即可以随机访问到对应元素。
在这里插入图片描述

假设你知道某个节点下标为x ,那么其左子树下标为 x *2+1,其右子树下标为 x *2+2 ,其父节点下标为 (x-1)/2。

4.2大根堆和小根堆

每个结点的值都大于其左孩子和右孩子结点的值,称之为大根堆;
每个结点的值都小于其左孩子和右孩子结点的值,称之为小根堆。
在这里插入图片描述

4.3堆排序基本步骤

基本思想

  1. 首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
  2. 将顶端的数与末尾的数交换,此时末尾的数为最大值,剩余待排序数组个数为n-1
  3. 将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
4.3.1MAX-HEAPIFY

MAX-HEAPIFY是用于维护最大堆性质的重要过程。它的输入为一个数组A和一个下标i。在调用MAX-HEAPIFY的时候,我们假定根节点为LEFT(i)和RIGHT(i)的二叉堆都是大根堆,但这时A[i]有可能小于其孩子,这样就违背了大根堆的性质。
MAX-HEAPIFY通过让A[i]的值在大根堆中逐级下降,从而是的以下标为i为根节点的字数重新遵循大根堆的性质。

void adjust(vector<int>& arr, int len, int idx) {
	int left = 2 * idx + 1;//idx的左子节点
	int right = 2 * idx + 2;//idx的右子节点

	int maxidx = idx;
	//判断最大节点是不是左右子节点
	if (left<len && arr[left]>arr[maxidx])maxidx = left;
	if (right<len && arr[right]>arr[maxidx])maxidx = right;

	if (maxidx != idx) {
		swap(arr[maxidx], arr[idx]);//交换根节点和子节点的值
		adjust(arr, len, maxidx);//交换后的子节点值为插入的节点的值,继续迭代
	}
}

在建堆的时候,我们可以用自底向上的方法利用过程MAX-HEAPIFY把一个大小为n的数组转换为大根堆。子数组A(n/2+1…n)中的元素都是树的叶节点。每个叶节点都可以看成只包含一个元素的堆。建堆的过程就是堆树中的其他节点都调用一次MAX-HEAPIFY:

for (int i = n / 2 - 1; i >= 0; i--)
		//MAX-HEAPIFY(A,i);
		adjust(arr, size, i);
4.3.2构造堆

以下建堆过程与上述描述无关,是直接建堆。
将无序数组构造成一个大根堆(升序用大根堆,降序就用小根堆)
假设存在以下数组:
在这里插入图片描述
构造堆的流程如下:每次新插入的数据都与其父结点进行比较,如果插入的数比父结点大,则与父结点交换,否则一直向上交换,直到小于等于父结点,或者来到了顶端。
注:下图中红色节点为添加节点,紫色节点为交换节点对
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
或者

4.3.3利用大根堆进行排序

根据之前的堆生成操作,我们已经得到一个大根堆,下面将顶端的数与最后一位数交换,然后将剩余的数再构造成一个大根堆。然后依次操作即可,如下图所示:
注:红色代表已经排序完的节点,紫色代表要交换的节点
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.4总结

将上述流程总结如下:

  1. 将无序数组构造出一个大根堆
  2. 固定一个最大值,将剩余的数重新构造一个大根堆,重复此过程直到所有数都固定完毕
    堆排序c++代码如下:
/*堆排序*/
#include<iostream>
#include<vector>
using namespace std;

void heapSort(vector<int>& arr, int size);
void adjust(vector<int>& arr, int len, int idx);

//递归方式构建大根堆(len是arr的长度,idx是当前节点的下标)
void adjust(vector<int>& arr, int len, int idx) {
	int left = 2 * idx + 1;//idx的左子节点
	int right = 2 * idx + 2;//idx的右子节点

	int maxidx = idx;
	if (left<len && arr[left]>arr[maxidx])maxidx = left;
	if (right<len && arr[right]>arr[maxidx])maxidx = right;

	if (maxidx != idx) {
		swap(arr[maxidx], arr[idx]);
		adjust(arr, len, maxidx);
	}
}

void heapSort(vector<int>& arr, int size) {
	//构建大根堆,从最后一个节点的父节点开始向前遍历
	for (int i = size / 2 - 1; i >= 0; i--)
		adjust(arr, size, i);
	//调整大根堆
	for (int i = size - 1; i >= 1; i--) {
		swap(arr[0], arr[i]);
		adjust(arr, i, 0);
	}
}

int main() {
	vector<int> arr = { 8, 1, 14, 3, 21, 5, 7, 10 };
	heapSort(arr, arr.size());
	return 0;
}

参考文章:

5.希尔排序

有空再写,先附上个参考链接:
https://blog.csdn.net/lgl782519197/article/details/106000802

6.快速排序

参考视频:https://www.bilibili.com/video/BV1K44y1k79z
具体原理看上面链接里面的视频就够了,下面简单讲一下快排和附上c++代码:
对于包含n个数的输入数组来说,快速排序是一种最坏情况时间复杂度为O(n²)的排序算法。
虽然最坏情况时间复杂度很差,但是快速排序通常是实际排序应用中最好的选择,因为它的平均性能很好,它的期望时间复杂度为O(nlgn),而且O(nlgn)中隐含的常数因子非常小。
附代码:

//快速排序
void fastsort(vector<int>& nums, int left, int right) {
	if (left >= right)return;
	int q = partition(nums, left, right);
	fastsort(nums, left, q - 1);
	fastsort(nums, q + 1, right);
}
int partition(vector<int>& nums, int left, int right) {
	int i = left;
	for (int j = left; j <= right - 1; j++)
		if (nums[j] <= nums[right])
			swap(nums[i++], nums[j]);
	swap(nums[i], nums[right]);
	return i;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值