六种常用排序方式详解(C++实现)

最近面试经常会被问到关于排序方面的问题,正好这块也记得不太熟,索性就把最常用的六种排序方式来实现总结一下。顺便再解答一下我在面试中被问到的问题。

一、冒泡排序

主要思想:比较相邻两个数,大的数往后走

遍历两次:
  • 第一层遍历:需要遍历的趟数,每完成一趟,无序区减少一个数,有序区增加一个数
  • 第二层遍历:在无序区比较相邻的两个数,如果后面的数比前面的数大,则进行交换

代码实现

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

// 冒泡排序
void BubbleSort(vector<int> &nums) {
	int len = nums.size();
	for (int i = 0; i < len - 1; i++) { // 第i趟
		bool exchange = false;
		for (int j = 0; j < len - 1 - i; j++) {
			if (nums[j] > nums[j+1]) {
				swap(nums[j], nums[j+1]);
				exchange = true;
			}
		}
		if (!exchange) {
			return;
		}
	}
}

int main() {
	vector<int> nums = { 2, 4, 5, 7, 1, 3, 6, 8};
	int len = nums.size();
	BubbleSort(nums, 0, len - 1);
	for (int i = 0; i < nums.size(); i++) {
		cout << nums[i] << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

二、选择排序

主要思想:每次遍历数组找到最小的元素,放在对应的位置上

遍历两次:
  • 第一层遍历用来记录最小的数应该放的位置
  • 第二层遍历用来找到最小的数,然后和第一层遍历记录的位置的数进行交换

代码实现

// 选择排序
// 主要思想:
// 1、第一层遍历用来记录最小的数应该放的位置
// 2、第二层遍历用来找到最小的数,找打以后和第一层遍历记录下位置的数进行交换
void SelectSort(vector<int>& nums) {
	int len = nums.size();
	for (int i = 0; i < len - 1; i++) { // 第i趟,走n-1趟
		int min_idx = i;
		for (int j = i+1; j < len; j++) {
			if (nums[j] < nums[min_idx]) { // 找到最小的数
				min_idx = j;
			}
		}
		swap(nums[i], nums[min_idx]); // 然后交换
	}
}

int main() {
	vector<int> nums = { 2, 4, 5, 7, 1, 3, 6, 8};
	int len = nums.size();
	SelectSort(nums, 0, len - 1);
	for (int i = 0; i < nums.size(); i++) {
		cout << nums[i] << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

三、插入排序

主要思想:类似于抓牌,先抓一个数,再抓到的数比它大,放右边,没它大,放左边,以此类推……

代码实现

// 插入排序
void InsertSort(vector<int>& nums) {
	int len = nums.size();
	for (int i = 1; i < len; i++) {
		int temp = nums[i]; // 保存当前的数
		int j = i - 1; // 前一个数的下标
		while (j >= 0 && nums[j] > temp) { // 如果前一个数比当前的数大
			nums[j + 1] = nums[j]; // 就把前一个数往右移
			j -= 1;
		}
		nums[j + 1] = temp; // 否则就将该数插入带这个位置
	}
}

int main() {
	vector<int> nums = { 2, 4, 5, 7, 1, 3, 6, 8};
	int len = nums.size();
	MergeSort(nums, 0, len - 1);
	for (int i = 0; i < nums.size(); i++) {
		cout << nums[i] << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

四、快速排序

主要思想:先随机找到一个数字,然后把比这个数字小的放在左边,比这个数字大的放在右边,从而分成了两组。然后依次类推……

代码实现

int partition(vector<int>& nums, int left, int right) {
	int temp = nums[left];
	while (left < right) {
		while (left < right && nums[right] >= temp) { // 如果右边的数大于该数
			right--; // 则将指针左移
		}
		nums[left] = nums[right]; // 否则则交换两边的数
		while (left < right && nums[left] <= temp) { // 如果左边的数小于该数
			left++; // 则将指针右移
		}
		nums[right] = nums[left]; // 否则交换两边的数
	}
	nums[left] = temp; // 将之前暂时保存的数字归位
	return left;
}

void QuickSort(vector<int>& nums, int left, int right) {
	if (left < right) {
		int mid = partition(nums, left, right); // 分隔
		QuickSort(nums, left, mid - 1); // 对左边的序列进行排序
		QuickSort(nums, mid + 1, right); // 对右边的序列进行排序
	}
}

int main() {
	vector<int> nums = { 2, 4, 5, 7, 1, 3, 6, 8};
	int len = nums.size();
	QuickSort(nums, 0, len - 1);
	for (int i = 0; i < nums.size(); i++) {
		cout << nums[i] << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

五、堆排序

  • 建立堆(按顺序存储进行存储,然后从最后一个非叶子节点进行调整)
  • 得到堆顶元素,为最大元素(大根堆)
  • 去掉堆顶,将堆最后一个元素放到堆顶(目的是保持调整完依然是完全二叉树),此时可通过一次调整重新使堆有序
  • 堆顶元素为第二大元素
  • 重复步骤3,直到堆变空
// 堆排序
/*
1、建立堆(按顺序存储进行存储,然后从最后一个非叶子节点进行调整)
2、得到堆顶元素,为最大元素(大根堆)
3、去掉堆顶,将堆最后一个元素放到堆顶(目的是保持调整完依然是完全二叉树),此时可通过一次调整重新使堆有序
4、堆顶元素为第二大元素
5、重复步骤3,直到堆变空
*/
void sift(vector<int>& nums, int low, int high) { // 调整函数
	/*
	nums 要排序的数组
	low  堆的根节点位置
	high 堆的最后一个位置
	*/
	int i = low; // 指向根节点
	int j = 2 * i + 1; // 指向左孩子
	int temp = nums[low]; // 暂存根节点的数
	while (j <= high) {
		if (j + 1 <= high && nums[j + 1] > nums[j]) { // 如果右孩子有且比左孩子大
			j = j + 1; // 指向右孩子
		}
		if (nums[j] > temp) {
			nums[i] = nums[j];
			i = j; // 往下找一层
			j = 2 * i + 1;
		}
		else {
			nums[i] = temp;
			break;
		}
	}
	nums[i] = temp;
}

void HeapSort(vector<int> &nums) {
	int n = nums.size();
	for (int i = (n - 2) / 2; i >= 0; i--) {
		// i为建堆的时候调整部分的根的下标
		sift(nums, i, n - 1); // 建堆
	}
	for (int i = n - 1; i >= 0; i--) {
		swap(nums[0], nums[i]);
		sift(nums, 0, i - 1);
	}
}

int main() {
	vector<int> nums = { 2, 4, 5, 7, 1, 3, 6, 8};
	int len = nums.size();
	HeapSort(nums, 0, len - 1);
	for (int i = 0; i < nums.size(); i++) {
		cout << nums[i] << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

六、归并排序

代码实现

// 归并排序(归并的思想同样可以用在合并两个单链表的题目中)
void merge(vector<int>& nums, int low, int mid, int high) {
	int i = low; // 用两个索引来遍历两个要合并的序列
	int j = mid + 1;
	vector<int> ltemp; // 用一个数组暂存要合并的序列
	while (i <= mid && j <= high) {
		if (nums[i] < nums[j]) { // 对两个有序序列进行遍历排序,有一个序列遍历完结束循环
			ltemp.push_back(nums[i]);
			i += 1;
		}
		else {
			ltemp.push_back(nums[j]);
			j += 1;
		}
	}
	while (i <= mid) { // 将另一个没有遍历完的序列全部插入到temp中
		ltemp.push_back(nums[i]);
		i += 1;
	}
	while (j <= high) {
		ltemp.push_back(nums[j]);
		j += 1;
	}
	int k = ltemp.size();
	for (int i = 0; i < k; i++) {
		nums[low++] = ltemp[i]; // 将排序好的序列更新到nums数组中
		// 需要注意的一点是,nums的起始索引为low,结束索引为high,不能直接带入0和nums.size()计算!
		// 因为这是一个函数,当合并的序列为右边的区间时,nums的起始坐标就不是0了,所以这里更新要使用low和high
	}
}

void MergeSort(vector<int> &nums, int low, int high) {
	if (low < high) {
		int mid = (low + high) / 2; 
		//对左边的序列进行划分此条命令结束代表左边的序列已经有序(划分到只有一个元素一定是有序的)
		MergeSort(nums, low, mid); 
		//对右边的序列进行划分此条命令结束代表右边的序列已经有序(划分到只有一个元素一定是有序的)
		MergeSort(nums, mid + 1, high);
		// 合并两个有序序列
		merge(nums, low, mid, high); 
	}
}

int main() {
	vector<int> nums = { 2, 4, 5, 7, 1, 3, 6, 8};
	int len = nums.size();
	MergeSort(nums, 0, len - 1);
	for (int i = 0; i < nums.size(); i++) {
		cout << nums[i] << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

几种排序算法的特点

图片1

关于面试中问到的有关于排序算法的问题

为什么快速排序的效率要比堆排序的效率要高?

  • 在堆排序(小根堆)的时候,每次总是将最小的元素移除,然后将最后的元素放到堆顶,再让其自我调整。这样一来,有很多比较将是被浪费的,因为被拿到堆顶的那个元素几乎肯定是很大的,而靠近堆顶的元素又几乎肯定是很小的,最后一个元素能留在堆顶的可能性微乎其微,最后一个元素很有可能最终再被移动到底部,在堆排序里面有大量这种近乎无效的比较。
  • 堆排序的过程中,需要有效的随机存取。比较父节点和字节点的值大小的时候,虽然计算下标会很快完成,但是在大规模的数据中对数组指针寻址也需要一定的时间。而快速排序只需要将数组指针移动到相邻的区域即可。在堆排序中,会大量的随机存取数据;而在快速排序中,只会大量的顺序存取数据。随着数据规模的扩大,这方面的差距会明显增大。在这方面的时间开销来说,快速排序只会线性增长,而堆排序增加幅度很大,会远远大于线性。
  • 在快速排序中,每次数据移动都意味着该数据距离它正确的位置越来越近,而在堆排序中,类似将堆尾部的数据移到堆顶这样的操作只会使相应的数据远离它正确的位置,后续必然有一些操作再将其移动,即“做了好多无用功”
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
目 录 译者序 前言 第一部分 预备知识 第1章 C++程序设计 1 1.1 引言 1 1.2 函数与参数 2 1.2.1 传值参数 2 1.2.2 模板函数 3 1.2.3 引用参数 3 1.2.4 常量引用参数 4 1.2.5 返回值 4 1.2.6 递归函数 5 1.3 动态存储分配 9 1.3.1 操作符new 9 1.3.2 一维数组 9 1.3.3 异常处理 10 1.3.4 操作符delete 10 1.3.5 二维数组 10 1.4 类 13 1.4.1 类Currency 13 1.4.2 使用不同的描述方法 18 1.4.3 操作符重载 20 1.4.4 引发异常 22 1.4.5 友元和保护类成员 23 1.4.6 增加#ifndef, #define和#endif语句 24 1.5 测试与调试 24 1.5.1 什么是测试 24 1.5.2 设计测试数据 26 1.5.3 调试 28 1.6 参考及推荐读物 29 第2章 程序性能 30 2.1 引言 30 2.2 空间复杂性 31 2.2.1 空间复杂性的组成 31 2.2.2 举例 35 2.3 时间复杂性 37 2.3.1 时间复杂性的组成 37 2.3.2 操作计数 37 2.3.3 执行步数 44 2.4 渐进符号(O、 健?、 o) 55 2.4.1 大写O符号 56 2.4.2 椒?58 2.4.3 符号 59 2.4.4 小写o符号 60 2.4.5 特性 60 2.4.6 复杂性分析举例 61 2.5 实际复杂性 66 2.6 性能测量 68 2.6.1 选择实例的大小 69 2.6.2 设计测试数据 69 2.6.3 进行实验 69 2.7 参考及推荐读物 74 第二部分 数据结构 第3章 数据描述 75 3.1 引言 75 3.2 线性表 76 3.3 公式化描述 77 3.3.1 基本概念 77 3.3.2 异常类NoMem 79 3.3.3 操作 79 3.3.4 评价 83 3.4 链表描述 86 3.4.1 类ChainNode 和Chain 86 3.4.2 操作 88 3.4.3 扩充类Chain 91 3.4.4 链表遍历器类 92 3.4.5 循环链表 93 3.4.6 与公式化描述方法的比较 94 3.4.7 双向链表 95 3.4.8 小结 96 3.5 间接寻址 99 3.5.1 基本概念 99 3.5.2 操作 100 3.6 模拟指针 102 3.6.1 SimSpace的操作 103 3.6.2 采用模拟指针的链表 106 3.7 描述方法的比较 110 3.8 应用 111 3.8.1 箱子排序 111 3.8.2 基数排序 116 3.8.3 等价类 117 3.8.4 凸包 122 3.9 参考及推荐读物 127 第4章 数组和矩阵 128 4.1 数组 128 4.1.1 抽象数据类型 128 4.1.2 C++数组 129 4.1.3 行主映射和列主映射 129 4.1.4 类Array1D 131 4.1.5 类Array2D 133 4.2 矩阵 137 4.2.1 定义和操作 137 4.2.2 类Matrix 138 4.3 特殊矩阵 141 4.3.1 定义和应用 141 4.3.2 对角矩阵 143 4.3.3 三对角矩阵 144 4.3.4 三角矩阵 145 4.3.5 对称矩阵 146 4.4 稀疏矩阵 149 4.4.1 基本概念 149 4.4.2 数组描述 149 4.4.3 链表描述 154 第5章 堆栈 161 5.1 抽象数据类型 161 5.2 派生类和继承 162 5.3 公式化描述 163 5.3.1 Stack的效率 164 5.3.2 自定义Stack 164 5.4 链表描述 166 5.5 应用 169 5.5.1 括号匹配 169 5.5.2 汉诺塔 170 5.5.3 火车车厢重排 172 5.5.4 开关盒布线 176 5.5.5 离线等价类问题 178 5.5.6 迷宫老鼠 180 5.6 参考及推荐读物 188 第6章 队列 189 6.1 抽象数据类型 189 6
[源代码]Python算法详解, 积分商城个人中心 首 页 资源分类 资料属性 源码 软件 教程 电路图 手册 笔记 经验 习题答案 应用设计 技术资料 电子大赛 开发板 模块 驱动开发 移动开发 加密解密 压缩解压 编译器/仿真器 多媒体处理 图形图像 游戏 音视频 传感器 连接器 软件 Altium Designer Atmel studio CAD CAM Candence CCS Codewarrior CorelDRAW IAR ISE Keil Labview Libero IDE Matlab MDK Modelsim Multisim PADS Protel proteus Quartus Source Insight Visual Studio 编程语言 C C++ C# JAVA Objective-c VB 汇编 Matlab编程 Labview编程 Verilog HDL VHDL python ruby delphi SQL CGI Perl R Swift php ASP JSP .NET HTML javascript 其他 应用 嵌入式 单片机 电源 测试测量 工业控制 汽车电子 安防监控 医疗电子 通信网络 模拟技术 机械综合 显示光电 智能小车 消费电子 物联网 智能硬件 照明 电子基础 IC设计 串口调试 模拟电路 数字电路 ADC MOS 放大器 存储器 编解码 算法 DIY 技术热点 Android ARM AVR DSP EDA FPGA IOS Linux MIPS Msp430 Nucleus PCB PLC PLD STM Symbian ubuntu vxworks 常用软件 ebook 全部 提交最新搜索: stc32 DS18B20 数码 HDC1080 上升沿捕获转为下降沿捕获方法 防护电路 gd32l23您现在的位置是:首页 > 源码 > [源代码]Python算法详解推荐星级:12345 [源代码]Python算法详解 更新时间:2022-09-12 13:10:47大小:18M上传用户:Laspide查看TA发布的资源标签:python算法下载积分:2分评价赚积分(如何评价?)收藏评论(0)举报资料介绍基于Python分别讲解了算法是程序的灵魂,数据结构,常用算法思想,线性表、队列和栈,树,图,查找算法,内部排序算法,经典的数据结构问题,解决数学问题,经典算法问题,解决图像问题,游戏和算法等内容。以“技术解惑”贯穿全书,引领全面掌握算法的核心技术。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

loveCC_orange

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值