一、选择排序
选择排序是一种简单直观的排序算法。它的工作原理如下:
遍历数组以[i,end]作为未排序序列,找到最小值的下标minIndex。
不断重复:在当前轮次未排序序列中找到最小(大)元素,存放到当前轮次排序序列的起始位置。
1.1 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void selectSort (vector<int >& vec) { if (vec.size () == 0 || vec.size () == 1 ) return ; int vec_size = vec.size (); for (int i = 0 ; i < vec_size-1 ; ++i) { int k = i; for (int j = i; j < vec_size; ++j) { if (vec[j] < vec[k]) { k = j; } } swap (vec[i], vec[k]); } }
1.2 测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <typename T>void printVec (vector<T>& v) { for (auto x : v) { cout << x << " " ; } } int main () { vector<int > vec{ 23 ,1 ,23 ,5 ,1 ,4 ,13 ,7 ,1 }; selectSort (vec); printVec (vec); return 0 ; }
1.3 复杂度分析
时间复杂度 :O(n^2)
空间复杂度 :O(1),属于原地排序。
二、冒泡
选择排序 是记录最小值的下标,一轮下来后将最小值排在最前面;
而冒泡排序 是一轮中不断地进行判断与两两交换,最后将最大值排在最后面 。它的实现原理为:
从头开始,两两比较相邻元素,如果逆序则交换。
重复上述步骤,直到没有任何一对数字需要比较。
2.1 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void bubbleSort (vector<int >& vec) { int vec_size = vec.size (); if (vec_size == 0 || vec_size == 1 ) return ; for (int i = vec_size - 1 ; i > 0 ; --i) { for (int j = 0 ; j < i; ++j) { if (vec[j] > vec[j + 1 ]) { swap (vec[j], vec[j + 1 ]); } } } }
2.2 测试
1 2 3 4 5 6 7 int main () { vector<int > vec{ 23 ,1 ,23 ,5 ,1 ,4 ,13 ,7 ,1 }; bubbleSort (vec); printVec (vec); return 0 ; }
2.3 优化
如果某一轮下来没有发生交换 ,说明已经有序 ,可以提前结束排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void bubbleSort (vector<int >& vec) { int vec_size = vec.size (); if (vec_size == 0 || vec_size == 1 ) return ; for (int i = vec_size - 1 ; i > 0 ; --i) { bool swapFlag = false ; for (int j = 0 ; j < i; ++j) { if (vec[j] > vec[j + 1 ]) { swap (vec[j], vec[j + 1 ]); swapFlag = true ; } } if (!swapFlag) { cout << "轮次:" << vec_size - i << endl; return ; } } }
2.4 复杂度分析
时间复杂度和空间复杂度跟选择排序一样。
时间复杂度 :O(n^2),在优化下,若数组已经有序,时间复杂度为O(n)。
空间复杂度 :O(1),属于原地排序。
三、插入排序
插入排序是一种简单直观的排序算法,其思想与扑克牌排序类似。它的工作原理如下:
从第一个元素开始,该元素可以认为已经被排序。
取出下一个元素j
,在已经排序的元素序列中从后向前扫描,扫描范围为k=[j-1,0]
。
如果vec[j] < vec[k]
,则将vec[k]
向后移动一个位置,直到找到前面已排序数组中第一个vec[j] >= vec[k]
。
3.1 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void insertSort (vector<int >& vec) { int vec_size = vec.size (); if (vec_size == 0 || vec_size == 1 ) return ; for (int i = 1 ; i < vec_size; i++) { int val = vec[i]; int j = i - 1 ; while (j >= 0 && vec[j] >= val) { vec[j + 1 ] = vec[j]; j--; } vec[j + 1 ] = val; } }
3.2 复杂度分析
时间复杂度 :O(n^2),在数组本身已经有序的情况下,时间复杂度为O(n)。
空间复杂度 :O(1),属于原地排序。
四、快速排序
快速排序是一种分治的排序算法。它通过将一个数组分成两个子数组 ,将左右两部分独立地不断递归 实现排序。
本文参考的是B站上的一个Up主的思路,但是代码是原创实现的。全网最清晰快速排序,看完快排思想和代码全部通透,不通透你打我!
4.1 思路
选择一个基准值,将数组分成两部分,左边的值都小于基准值,右边的值都大于基准值。
递归地对左右两部分进行排序。
4.2 代码实现
4.2.1 根据视频实现
先实现一个分区函数quickSort_midpos
,将数组分成两部分,左边的值都小于基准值,右边的值都大于基准值。并返回基准值的位置用于下一轮左右两部分的递归排序。
递归函数quickSort
不断调用quickSort_midpos
对左右两部分进行排序。
设计静态函数myQuickSortTest
用于测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #pragma once #include <iostream> #include <vector> #include <algorithm> #include <string> using namespace std;class myQuickSort { public : void printVect (vector<int >& arr) { for (int num : arr) { cout << num << " " ; } cout << endl; } void quickSort (vector<int > &arr, int left, int right) { if (right > left) { int pos = quickSort_midpos (arr, left, right); quickSort (arr, left, pos - 1 ); quickSort (arr, pos + 1 , right); } } int quickSort_midpos (vector<int >& arr, int left, int right) { int pos_val = arr[left]; int empty_index = left; while (left != right) { if (empty_index == left) { if (arr[right] <= pos_val) { swap (arr[empty_index], arr[right]); empty_index = right; left++; } else { right--; } } else if (empty_index == right) { if (arr[left] >= pos_val) { swap (arr[left], arr[empty_index]); empty_index = left; right--; } else { left++; } } } arr[left] = pos_val; return right; } static void myQuickSortTest () { cout << "Please input some nums(int) and use exit to stop inputting numbers:" << endl; vector<int > arr; int num; string strIn; while (cin >> strIn) { if (strIn == "exit" ) { break ; } try { num = stoi (strIn); if (to_string (num) != strIn) { cout << "Error!! Please input a num(int) again!" << endl; } else { arr.push_back (num); } } catch (const std::exception&) { cout << "Invalid Input! Please input a num(int) again!" << endl; } } myQuickSort QS; QS.quickSort (arr, 0 , arr.size () - 1 ); QS.printVect (arr); cout << "调试用" << endl; } };
测试
1 2 3 4 5 #include "myQuickSort.h" int main () { myQuickSort::myQuickSortTest (); return 0 ; }
4.2.2 根据Hello算法实现
Hello算法 中的思想更为简单明了,在一个[left,right]
范围内查找pos
时的思路为:
以第一个数nums[left]
作为基准值base
,将双指针分别赋值为low = left
和high = right
从右向左移动high
,找到第一个小于base
的数
从左向右移动low
,找到第一个大于base
的数
交换nums[low]
和nums[high]
重复上述步骤,直到low
和high
相遇,相遇后就剩最后一个位置需要交换,将base
和nums[low]
交换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #ifndef QUICKSORT_H #define QUICKSORT_H #include <iostream> #include <vector> using namespace std;class quicksort {public : int position (vector<int >&nums, int left, int right) { int baseNum = nums[left]; int low = left; int high = right; while (low < high) { while (low < high && nums[high] >= baseNum) --high; while (low < high && nums[low] <= baseNum) ++low; swap (nums[low], nums[high]); } swap (nums[left], nums[low]); return low; } void doQuickSort (vector<int > &nums, int left, int right) { if (left >= right) return ; int pos = position (nums, left, right); doQuickSort (nums, left, pos - 1 ); doQuickSort (nums, pos+1 , right); } }; #endif
代码中的position
函数是用来找到基准值的位置,quickSort
函数是递归调用position
函数对左右两部分进行排序。
4.3 复杂度分析
时间复杂度 :O(nlogn),最坏情况下为O(n^2)。
空间复杂度 :最坏情况是O(n),在输入数组完全倒序的情况下,达到最差递归深度n,使用O(n)栈帧空间。
4.4 快排为什么快?
出现最坏情况的概率很低
快速排序快的主要原因是大大减少了比较和交换的次数 ,因为按基准数切分的两半数组,在一个数组里面的数据是绝对不会和第二个数组里面的数字产生比较的机会的,所以大幅度降低了做无用功的机会。
五、归并排序
5.1 思路
归并排序是一种分治 的排序算法,思想是先按照左右中 的顺序,具体步骤如下:
左右 :先不断递归地将数组分 成两半,直到每个子数组只有一个元素后return回溯,得到某一层的左右两个有序数组(默认一个元素是有序的)
中 :将左右两个有序数组合并 成一个有序数组,期间需要用到临时数组 来存放合并后的有序数组
5.2 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 void merge (vector<int >& vec, int left, int right) { int LR_size = right - left + 1 ; vector<int > tmpVec (LR_size) ; int mid = (left + right) / 2 ; int left_p = left, right_p = mid+1 ; int i = 0 ; while (left_p <= mid && right_p <= right) { if (vec[left_p] <= vec[right_p]) { while (left_p<=mid && vec[left_p] <= vec[right_p]) { tmpVec[i++] = vec[left_p++]; } } else { while (right_p <= right && vec[left_p] > vec[right_p]) { tmpVec[i++] = vec[right_p++]; } } } if (left_p <= mid) { while (left_p <= mid) { tmpVec[i++] = vec[left_p++]; } } else if (right_p <= right) { while (right_p <= right) { tmpVec[i++] = vec[right_p++]; } } left_p = left; for (auto tmp : tmpVec) { vec[left_p++] = tmp; } } void mergeSort (vector<int >& vec, int left, int right) { if (left >= right) return ; int mid = (left + right) / 2 ; mergeSort (vec, left, mid); mergeSort (vec, mid+1 , right); merge (vec, left, right); }
5.3 复杂度分析
时间复杂度 :O(nlogn),划分产生高度为lg(n)
的递归树,每层合并的总操作数量为O(n)
空间复杂度 :O(n),合并操作需要一个临时数组来存放合并后的有序数组。
六、堆排序
6.1 思路
未完待续......
七、总结
各种算法的复杂度总结:
参考:Hello
算法