需重点掌握的三大排序: 快速排序 + 归并排序 + 堆排序

关于排序算法的考察,需重点掌握   快速排序 、归并排序  、 堆排序 。

1.  快速排序

    1.1 快速排序算法的思想与实现

     快速排序的重点是要掌握其划分的思想,划分算法需要先挑选一个 元素作为 pivot。在划分执行完之后,

pivot左边的元素都小于等于它,右边的元素都大于等于它,pivot 的位置就是数组排序完成后它的最终位置。

每执行一次 划分算法,确定数组中一个元素的位置。

 

 1 //1. 快速排序
 2     void quickSort(vector<int>& nums)
 3     {
 4         const int len = nums.size();
 5         if(len <= 1) return;
 6         std::random_shuffle(nums.begin(),nums.end());//打乱是为了避免快速排序陷入最差情况
 7         quickSortHlper(nums,0,len - 1);
 8         return;
 9     }
10      void quickSortHlper(vector<int>& nums,int l,int r)
11      {
12          
13         if(l >= r) return;
14         int mid = Partition(nums,l,r);
15         quickSortHlper(nums,l,mid - 1);
16         quickSortHlper(nums,mid+1,r);
17          return;
18      }
19     //划分
20     int Partition(vector<int>& nums,int l,int r)
21     {
22         int i = l + 1, j = r;
23         while(true)
24         {
25             while(i <= j && nums[i] <= nums[l]) i++;
26             while(i <= j && nums[j] >= nums[l]) j--;
27             // 1. 此时 [j+1,r]的元素一定都比 nums[l]大,[l+1,i-1] 内的元素一定都比 nums[l]小
28             // 2. 因为 i > j ,[l+1,j] 一定是[l+1,i-1] 的子集,[l+1,j] 内的元素一定都比 nums[l]小
29             // 3. [l+1,j] 内的元素一定都比 nums[l]小,[j+1,r]的元素一定都比 nums[l]大,所以
30             //执行 std::swap(nums[l],nums[j])后,j 就是nums[l]最终的位置。
31             if(i > j)//此时 [j+1,r]的元素一定都比 nums[l]大,[l+1,j] 内的元素一定都比 nums[l]小
32             {
33                 std::swap(nums[l],nums[j]);
34                 break;
35             }
36             std::swap(nums[i],nums[j]);
37         }
38         return j;
39     }

       1.2 快速排序算法的应用: LeetCode   215. 数组中的第K个最大元素

       

 1 class Solution {
 2 public:
 3     int findKthLargest(vector<int>& nums, int k)
 4     {
 5         const int N = nums.size();
 6         int k_max_index = N - k;
 7         int l = 0;
 8         int r = N -1;
 9         while(l <= r)
10         {
11             int mid = quickSelect(nums,l,r);
12             if(mid == k_max_index)
13             {
14                 return nums[mid];
15             }
16             else if(mid < k_max_index)
17             {
18                 l = mid + 1;
19             }
20             else
21             {
22                 r = mid - 1;
23             }
24         }
25         return INT_MAX;//不会执行到这一步
26     }
27     
28 //快速排序划分算法,以 nums[l] 为 pivot,执行完成,确定 nums[l]最终排序好的位置
29     int quickSelect(vector<int>& nums, int l,int r)
30     {
31         int i = l + 1, j = r;
32         while(true)
33         {
34             while(i <= j && nums[i] <= nums[l]) i++;
35             while(i <= j && nums[j] >= nums[l]) j--;
36             // 1. 此时 [j+1,r]的元素一定都比 nums[l]大,[l+1,i-1] 内的元素一定都比 nums[l]小
37             // 2. 因为 i > j ,[l+1,j] 一定是[l+1,i-1] 的子集,[l+1,j] 内的元素一定都比 nums[l]小
38             // 3. [l+1,j] 内的元素一定都比 nums[l]小,[j+1,r]的元素一定都比 nums[l]大,所以
39             //执行 std::swap(nums[l],nums[j])后,j 就是nums[l]最终的位置。
40             if(i > j)//此时 [j+1,r]的元素一定都比 nums[l]大,[l+1,j] 内的元素一定都比 nums[l]小
41             {
42                 std::swap(nums[l],nums[j]);
43                 break;
44             }
45             std::swap(nums[i],nums[j]);
46         }
47         return j;
48     }
49 };

 

2. 归并排序

   2.1 归并排序的思想与实现

    归并排序是分治思想的典型应用,使用了一个辅助数组。时间 O(N *logN) 空间 (N)

          

 1 void mergeSort(vector<int>& nums)
 2     {
 3         const int len = nums.size();
 4         if(len <= 1) return;
 5         vector<int> tmp(len);
 6         mergeSortHelper(nums,0,len - 1,tmp);
 7         return;
 8     }
 9     void mergeSortHelper(vector<int>& nums,int l,int r,vector<int>& tmp)
10     {
11         if(l >= r)
12         {
13             return;
14         }
15         int mid = l + (r - l)/2;
16         mergeSortHelper(nums,l,mid,tmp);
17         mergeSortHelper(nums,mid+1,r,tmp);
18         if(nums[mid] <= nums[mid+1])
19         {
20             return;
21         }
22         int i = l, j = mid + 1,k = 0;
23         while(i <= mid && j <= r)
24         {
25             if(nums[i] <= nums[j])
26             {
27                 tmp[k++] = nums[i++];
28             }
29             else
30             {
31                 tmp[k++] = nums[j++];
32             }
33         }
34         while(i <= mid)
35         {
36             tmp[k++] = nums[i++];
37         }
38         while(j <= r)
39         {
40             tmp[k++] = nums[j++];
41         }
42         for(i = l,k = 0; i <= r;++i)
43         {
44             nums[i] = tmp[k++];
45         }
46         return;
47     }

 

      2.2  归并排序算法的应用: LeetCode    剑指 Offer 51. 数组中的逆序对

    

 1 class Solution {
 2 public:
 3     vector<int> tmp;//归并排序的辅助数组
 4 
 5     int reversePairs(vector<int>& nums) {
 6         const int len = nums.size();
 7         if(len < 2) return 0;
 8         tmp.assign(len,0);
 9         return merge_sort(nums,0,len - 1);
10     }
11  
12     long long int merge_sort(vector<int>& nums,int l,int r)
13     {
14         if(l >= r)//归并排序递归出口
15         {
16             return 0;
17         }
18         int mid = l + (r - l)/2;//将区间一分为二
19         long long ans = merge_sort(nums,l,mid) + merge_sort(nums,mid + 1,r);//递归地求左右子数组的逆序对个数和
20         //剪枝优化: 子数组[l,r] 已经有序,横跨两个区间的逆序对个数为 0
21         if(nums[mid + 1] >= nums[mid])
22         {
23            return ans;
24         }
25         //计算 横跨左右区间的逆序对
26         int k = 0,i = l,j = mid + 1;
27         while(i <= mid && j <= r)
28         {
29             if(nums[i] <= nums[j])
30             {
31                 tmp[k++] = nums[i++];
32             }
33             else
34             { //执行上述递归之后,左右子数组都已经排好序
35               //此处,nums[i:mid] 都比nums[j] 大,都和num[j] 构成逆序对
36                 ans += (mid - i + 1);
37                 tmp[k++] = nums[j++];
38             }
39         }
40         //将左右区间未移动到tmp的继续移到tmp
41         while(i <= mid) tmp[k++] = nums[i++];
42         while(j <= r) tmp[k++] = nums[j++];
43         //将排序好的元素移回原数组,放在原来的区间上,然后nums[l:r]已经排序好了 
44         for(i = l,j = 0;i <= r; )
45         {
46             nums[i++] = tmp[j++];
47         }
48         return ans;
49     }
50 };

 

3.   堆排序

  3.1 堆排序 是在数组上建立一个完全二叉树的堆,堆排序分为 建堆 和 排序 两个过程。

    建堆,通过下沉操作建堆效率更高,具体过程是,从最后一个非叶子节点开始,然后从后往前,对每个非叶子节点 执行下沉操作。

这样之后,nums[0:N-1]上就是一个大顶堆。

    排序,将堆顶元素(代表最大元素)与堆的最后一个元素交换,同时从堆范围缩小一个元素,然后新的堆顶元素进行下沉操作,

遍历执行上诉操作,则可以完成排序。

     

 1 //3. 堆排序 
 2     void heapSort(vector<int>& nums)
 3     {
 4        int len = nums.size();
 5        for(int i = (len - 1)/2; i >= 0; --i)
 6        {
 7            siftDown(nums,i,len -1);
 8        }
 9        for(int i = len -1 ;i >= 1;)
10        {
11            swap(nums[0],nums[i]);
12            siftDown(nums,0,--i);
13        }
14        return;
15     }
16  /**
17      * @param nums
18      * @param k    当前下沉元素的下标
19      * @param end  [0, end] 是堆的有效部分,[end+1,nums.size() - 1] 是nums中排好序的部分
20      */
21     void siftDown(vector<int>& nums,int k,int end)
22     { 
23         while(2 * k + 1 <= end)// k为叶节点跳出
24         {
25             int j = 2 * k + 1 ;//j是k的左子节点索引
26             if(j + 1 <= end && nums[j+1] >= nums[j])
27             {
28                 j++;
29             }
30             //nums[j]是 nums[k]值最大的子节点
31             if(nums[j] > nums[k])
32             {
33                 std::swap(nums[k],nums[j]);
34             }
35             else
36             {
37                 break;
38             }
39             k = j;
40         }
41     }

      3.2    堆排序的应用,c++ STL 中 的 优先队列  std::priority_queue 就是堆实现的。

 LeetCode    剑指 Offer 51. 数组中的逆序对  同样可以使用堆解决。

 1    int findKthLargest(vector<int>& nums, int k) 
 2     {
 3         std::priority_queue<int,vector<int>,greater<int>> pq;
 4         for(auto num : nums)
 5         {
 6             if(pq.size() == k && pq.top() >= num ) continue;
 7             if(pq.size() == k)
 8             {
 9                 pq.pop();
10             }
11             pq.push(num);
12         }
13         return pq.top();
14     }

 

     

 

posted @ 2021-04-18 22:11  谁在写西加加  阅读(177)  评论(0编辑  收藏  举报