【数据结构-排序】3.图解选择排序两种实现(简单选择排序/堆排序)
簡單選擇排序(選擇排序)
排序思想
下面舉個示例:
第一輪排序,i = 0,j = 1,min = 23,tmp = 23(tmp是用來記錄當前需要調整位置的值),pos = 0(記錄比min小的元素位置)。遍歷,當 j=7 時,min = 18,pos=7。然后將 nums[i] = nums[pos],j++,pos = i++
第二輪排序,i = 1,j = 2,min = 76,tmp = 76,pos = 1。遍歷,當 j = 2 時,min = 34(黃標),pos = 2;繼續遍歷,當 j = 7 時,min = 23,pos = 7。然后將 nums[i] = nums[pos],j++,pos = i++
第三輪排序,i = 2,j = 3 ,min = 34(黃標),tmp = 34(黃標),pos = 2。遍歷,發現沒有比 34(黃標)更小的元素了。然后將 j++,pos = i++
第四輪排序,i = 3,j = 4,min = 34(綠標),tmp = 34(綠標),pos = 3。遍歷,發現沒有比 34(綠標)更小的元素了,j++,pos = i++
第五輪排序,i = 4,j = 5,min = 90,tmp = 90,pos = 4;遍歷,當 j = 5 時,min = 89 ,pos = 5 ;繼續遍歷,當 j = 6 時,min = 56,pos = 6;然后將 nums[i] = nums[pos],j++,pos = i++
第六輪排序,i = 5,j = 6,min = 89,tmp = 89,pos = 5;遍歷,當 j = 7 時,min = 76,pos = 5;繼續遍歷,當 j = 6 時,min = 56,pos = 6;然后將 nums[i] = nums[pos],j++,pos = i++
第六輪排序,i = 6,j = 7,min = 90,tmp = 90,pos = 6;遍歷,當 j = 7 時,min = 89,pos = 7;然后將 nums[i] = nums[pos],j++,pos = i++
簡單選擇排序算法分析
簡單選擇排序的時間復雜度是 O(n^2),空間復雜度是 O(1),同時也是 不穩定排序(嘗試將18改為34(綠標)初始位置換一下即可知),但是是一種 全局有序 的排序算法。
代碼實現
class Solution{ public:void selectionSort(vector<int> &nums) {for(int i = 0; i < nums.size() - 1; i++) {int min = nums[i];int tmp = nums[i];int pos = i;for(int j = i+1; j < nums.size(); j++) {if(min > nums[j]) {min = nums[j];pos = j;}}nums[i] = nums[pos];nums[pos] = tmp;} } };加工后執行的結果
堆排序(選擇排序)
排序思想
堆排序是一種屬性選擇方法,它的特點是:在排序過程中,將 nums[0,n-1] 視為一棵完全二叉樹的順序結果,之所以采用完全二叉樹,是因為完全二叉樹與列表下標之間存在數學關系,比如完全二叉樹中雙親結點和孩子結點之間的內在關系,在當前無需去中選擇關鍵字最大(最小)的元素
在介紹堆排序之前,首先要介紹一下 最大堆 和 最小堆,這是堆排序的前置基礎。
大頂堆:nums[i] >= nums[2i+1] && nums[i] >= nums[2i+2]
大頂堆的文字描述:父結點的值比子結點的值都要大與或等于
小頂堆:nums[i] <= nums[2i+1] && nums[i] <= nums[2i+2]
小頂堆的文字描述:父結點的值比子結點的值都要小于或等于
詳解堆排序的過程,首先是構造最大堆
i = len / 2 - 1,此時 i = 2
令 k = 2 * i + 1 = 5(若存在左子結點,得到左子結點,即 k <= len)
nums[k] = 34 ,nums[k++] =56(若存在兄弟結點),兄弟結點比較,將指針 k 指向較大的結點
將較大的子結點與父結點比較,若子結點大于父結點,就交換位置
調整 k = 13 的結點(無)
i --,此時 i = 1
令 k = 2 * i + 1 = 3(若存在左子結點,得到左子結點)
nums[k] = 89 ,nums[k++] =90(若存在兄弟結點,即 k+1 < len),兄弟結點比較,將指針 k 指向較大的結點
將較大的子結點與父結點比較,若子結點大于父結點,就交換位置
調整 k = 9 的結點(無)
i --,此時 i = 0
令 k = 2 * i + 1 = 1(若存在左子結點,得到左子結點)
nums[k] = 90 ,nums[k++] =56(若存在兄弟結點,即 k+1 < len),兄弟結點比較,將指針 k 指向較大的結點
將較大的子結點與父結點比較,若子結點大于父結點,就交換位置
調整 k = 3的結點(無)
之后就是堆排序的內容,每一次排序之后,都要重新調整一次堆的結構,使堆稱為大頂堆或者小頂堆
第一輪調整:當調整完大頂堆之后,將大頂堆堆頂元素與下標為i的元素交換。也就是說,i=6 , nums[6]=90(堆頂元素),nums[0]=34(黃標)
第二輪調整:上一輪調整完畢之后,i-1,也就是說 i=5,按上面構造大頂堆的方法繼續從下往上調整堆的元素,直至完成大頂堆。
當調整完大頂堆之后,將大頂堆堆頂元素與下標為 i 的元素交換。也就是說,i=5,nums[5]=89(堆頂元素),nums[0]=34(綠標)
第三輪調整:上一輪調整完畢之后,i-1,也就是說 i=4,按上面構造大頂堆的方法繼續從下往上調整堆的元素,直至完成大頂堆。
當調整完大頂堆之后,將大頂堆堆頂元素與下標為i的元素交換。也就是說,i=4,nums[4]=76(堆頂元素),nums[0]=34(黃標)
第四輪調整:上一輪調整完畢之后,i-1,也就是說 i=3,按上面構造大頂堆的方法繼續從下往上調整堆的元素,直至完成大頂堆。
當調整完大頂堆之后,將大頂堆堆頂元素與下標為 i 的元素交換。也就是說,i=3,nums[3]=56(堆頂元素),nums[0]=23
第五輪調整:上一輪調整完畢之后,i-1,也就是說 i=2,按上面構造大頂堆的方法繼續從下往上調整堆的元素,直至完成大頂堆。
當調整完大頂堆之后,將大頂堆堆頂元素與下標為i的元素交換。也就是說,i=2,nums[2]=34(黃標,堆頂元素),nums[0]=23
最后輪調整:上一輪調整完畢之后,i-1,也就是說 i=1,按上面構造大頂堆的方法繼續從下往上調整堆的元素,直至完成大頂堆。
當調整完大頂堆之后,將大頂堆堆頂元素與下標為i的元素交換。也就是說,i=1,nums[1]=34(綠標,堆頂元素),nums[0]=23
了解了樣式原理,就可以提煉總結
整個堆排序可以分為三個部分
第一部分,就是堆排序的本體
第二部分,調整堆的位置
堆是一顆完全二叉樹,也就是說,堆滿足完全二叉樹的全部性質,比如,父結點是 nums[i] 的話,子結點的位置分別是 nums[2i+1] 和 nums[2i+2](假設len > 2i + 2的話),因此就有如下調整步驟
第三部分,交換元素位置
在調整為大頂堆之后
堆要注意的
堆排序向下調整的時間與樹高有關,為 O(h)
堆序算法分析
堆排序的時間復雜度是 O(nlogn),空間復雜度是 O(1),但是通過前面示例中 34 和黃標和綠標的相對位置可以得知,堆排序是 不穩定排序,但它是 全局有序。
代碼實現
class Solution{ public:void heapSort(vector<int> &nums) {// 1.構造大頂堆for(int i = nums.size() / 2 - 1; i >= 0; i--) {//從第一個非葉子結點從下至上,從右至左調整結構adjustHeap(nums,i,nums.size());} //2.調整堆結構+交換堆頂元素與末尾元素for(int i = nums.size() - 1; i > 0; i--){//將堆頂元素與末尾元素進行交換swap(nums, 0, i);//重新對堆進行調整adjustHeap(nums, 0, i);}} private://調整大頂堆(僅是調整過程,建立在大頂堆已構建的基礎上)void adjustHeap(vector<int> &nums, int i, int len) {// 先取出當前元素iint temp = nums[i];// 從i結點的左子結點開始,也就是2i+1處開始for(int k = 2 * i + 1; k < len; k = 2 * k + 1) {// 如果左子結點小于右子結點,k指向右子結點if(k + 1 < len && nums[k]<nums[k+1]){k++;}//如果子結點大于父結點,將子結點值賦給父結點(不用進行交換)if(nums[k] > temp){nums[i] = nums[k];i = k;}else{break;}}//將temp值放到最終的位置nums[i] = temp;}// 交換元素void swap(vector<int> &nums, int a ,int b){int temp=nums[a];nums[a] = nums[b];nums[b] = temp;} };加工后執行的結果
簡單選擇排序測試代碼
#include <stdio.h> #include <vector> using namespace std;class Solution{ public:void selectionSort(vector<int> &nums) {printf("第0輪:");for(int j = 0; j < nums.size(); j++) {printf("%d",nums[j]);if(j!=nums.size()-1) printf(",");}printf("\n");for(int i = 0; i < nums.size() - 1; i++) {int min = nums[i];int tmp = nums[i];int pos = i;for(int j = i+1; j < nums.size(); j++) {if(min > nums[j]) {min = nums[j];pos = j;}}nums[i] = nums[pos];nums[pos] = tmp;printf("第%d輪:", i+1);for(int j = 0; j < nums.size(); j++) {printf("%d",nums[j]);if(j!=nums.size()-1) printf(",");}printf("\n");} } };int main() {vector<int> v;v.push_back(23);v.push_back(76);v.push_back(34);v.push_back(89);v.push_back(90);v.push_back(34);v.push_back(56);v.push_back(18);Solution solution;solution.selectionSort(v);return 0; }堆排序測試代碼
#include <stdio.h> #include <vector> using namespace std;class Solution{ public:void heapSort(vector<int> &nums) {// 1.構造大頂堆for(int i = nums.size() / 2 - 1; i >= 0; i--) {//從第一個非葉子結點從下至上,從右至左調整結構adjustHeap(nums,i,nums.size());} int counter = 0;//2.調整堆結構+交換堆頂元素與末尾元素for(int i = nums.size() - 1; i > 0; i--){printf("第%d輪:", counter++);for(int j = 0; j < nums.size(); j++) {printf("%d",nums[j]);if(j!=nums.size()-1) printf(",");}printf("\n");//將堆頂元素與末尾元素進行交換swap(nums, 0, i);//重新對堆進行調整adjustHeap(nums, 0, i);}printf("第%d輪:", counter++);for(int j = 0; j < nums.size(); j++) {printf("%d",nums[j]);if(j!=nums.size()-1) printf(",");}printf("\n");} private://調整大頂堆(僅是調整過程,建立在大頂堆已構建的基礎上)void adjustHeap(vector<int> &nums, int i, int len) {//先取出當前元素iint temp = nums[i];//從i結點的左子結點開始,也就是2i+1處開始for(int k = 2 * i + 1; k < len; k = 2 * k + 1) {//如果左子結點小于右子結點,k指向右子結點if(k + 1 < len && nums[k]<nums[k+1]){k++;}//如果子結點大于父結點,將子結點值賦給父結點(不用進行交換)if(nums[k] > temp){nums[i] = nums[k];i = k;}else{break;}}//將temp值放到最終的位置nums[i] = temp;}// 交換元素void swap(vector<int> &nums, int a ,int b){int temp=nums[a];nums[a] = nums[b];nums[b] = temp;} };int main() {vector<int> v;v.push_back(23);v.push_back(76);v.push_back(34);v.push_back(89);v.push_back(90);v.push_back(34);v.push_back(56);Solution solution;solution.heapSort(v);return 0; }總結
以上是生活随笔為你收集整理的【数据结构-排序】3.图解选择排序两种实现(简单选择排序/堆排序)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【LeetCode】0830.较大分组的
- 下一篇: 【数据结构-排序】4.图解归并排序和基数