六种常用排序方式详解(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

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

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

  • 在堆排序(小根堆)的时候,每次总是将最小的元素移除,然后将最后的元素放到堆顶,再让其自我调整。这样一来,有很多比较将是被浪费的,因为被拿到堆顶的那个元素几乎肯定是很大的,而靠近堆顶的元素又几乎肯定是很小的,最后一个元素能留在堆顶的可能性微乎其微,最后一个元素很有可能最终再被移动到底部,在堆排序里面有大量这种近乎无效的比较。
  • 堆排序的过程中,需要有效的随机存取。比较父节点和字节点的值大小的时候,虽然计算下标会很快完成,但是在大规模的数据中对数组指针寻址也需要一定的时间。而快速排序只需要将数组指针移动到相邻的区域即可。在堆排序中,会大量的随机存取数据;而在快速排序中,只会大量的顺序存取数据。随着数据规模的扩大,这方面的差距会明显增大。在这方面的时间开销来说,快速排序只会线性增长,而堆排序增加幅度很大,会远远大于线性。
  • 在快速排序中,每次数据移动都意味着该数据距离它正确的位置越来越近,而在堆排序中,类似将堆尾部的数据移到堆顶这样的操作只会使相应的数据远离它正确的位置,后续必然有一些操作再将其移动,即“做了好多无用功”

猜你喜欢

转载自blog.csdn.net/loveCC_orange/article/details/126336765
今日推荐