LeetCode刷题之排序算法
LeetCode刷题笔记
排序算法
稳定与不稳定
内排序和外排序
内排序:待排序的所有记录都放置在内存中。
外排序:由于记录的个数太多,不能同时放在内存中,整个排序过程需要在内外存之间多次交换数据才能进行。
//排序所有的顺序表结构
#define MAXSIZE 10000;
typedef struct{
int r[MAXSIZE+1];
int length;
}SqList;
//排序过程中用到的元素交换函数
void swap(SqList* L,int i,int j){
int temp;
temp = L->r[i];
L->r[i] = L->r[j];
L->r[j] = temp;
}
1.插入排序
1.插入排序
-
基本思想
将一个记录插入到一个已经排好序的有序表中,从而得到一个新的,记录数增一的有序表。
-
代码实现
void InsertSort(SqList *L) { int i, j; for (i = 2; i <= L->length; i++) { if (L->r[i] < L->r[i-1]) { L->r[0] = L->r[i]; for(j = i-1; L->r[j] > L->r[0]; j--) { L->r[j+1] = L->r[j]; } L->r[j+1] = L->r[0]; } } }
-
算法复杂度和稳定性分析
最好的情况是O(n),最坏的情况是O(n^2)。算法是稳定的。
2.希尔排序
-
基本思想
希尔排序的本质还是插入排序,但是其与直接插入排序不同的是它会将原序列分成几个小序列,然后再分别对其进行排序,然后逐渐增大子序列的长度,最后,当子序列长度为1时,排序结束。
代码思路及图解链接 希尔排序
-
代码实现
void ShellSort(SqList *L) { int i, j, k = 0; int increment = L->length;//当inrecment == 1时,子序列即为全局序列 do { increment = increment / 3 + 1;//选择序列增量,即每个子序列中的相邻元素序号在全局序列中序列差值为increment for (i = increment + 1; i <= L->length; i++) {//下面的步骤与直接插入排序的思路一致,几个子序列分别进行直接插入排序 if(L->r[i] < L->[i-increment]) { L->r[0] = L->r[i];//L->r[0]可用temp来代替 for (j = i - increment; j > 0 && L->r[0] < L->r[j]; j -= increment) {//对子序列进行直接插入操作 L->r[j+increment] = L-<r[j]; L->[j+increment] = L->r[0]; } } } } while(increment > 1); }
-
算法复杂度及稳定性分析
最好的情况是O(n1.5),最坏的情况是O(n2)。算法是不稳定的。
2.交换排序
1.冒泡排序
-
基本思想
冒泡排序属于一种交换排序,基本思想:是两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
-
代码实现
1.初级版本(从前往后比)
void BubbleSort(SqList* L) { int i,j; for (int i = 1; i < L->length; i++) { for (int j = i+1; j <= L->length; j++) { if (L->r[i] > L->r[j]) swap(L,i,j); } } }
2.改良版本(从后往前比)
void BubbleSort(SqList *L) { int i,j; for (int i = 1; i < L->length; i++) { for (int j = L->length; j >= i; j--) { if (L->r[j] > L->r[j+1]) { swap(L, j, j+1); } } } }
3.进阶版本(从后往前比,增加标志位)
void BubbleSort(SqList *L) { int i,j; bool flag = true; for (int i = 1; i < L->length && flag; i++) { flag = false; for (int j = L->length; j >= i; j--) { swap(L, j, j+1); flag = true; } } }
-
算法复杂度及稳定性分析
最好的情况为O(n),最坏的情况是O(n^2)。算法是稳定的。
2.快速排序
-
基本思想
快速排序的基本思想是:通过一段排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
-
代码实现
1.基本的代码实现
void QuickSort(SqList *L) { QSort(L, 1, L->length); } void QSort(SqList *L, int low, int high) { int pivot; if (low < high) { pivot = Partition(L,low,high);//找出中间枢纽 QSort(L, low, pivot-1);//对低子表进行递归排序 QSort(L, pivot+1, high);//对高子表进行递归排序 } } int Partition(SqList *L, int low, int high) { int piv; piv = L->r[low]; while (low < high) { while (low < high && L->r[high] >= piv) { --high; } swap(L, low, high); while (low < high && L->r[low] <= piv) { ++low; } swap(L, low, high); } return low; }
2.快速排序进行优化
-
优化选取中间枢纽
中间枢纽的选择快速排序中显得极为重要,选取一个合适的中间,可以大大减少排序过程中的交换。因此我们可以基于此对其进行优化。固定选取第一个关键字显得极不合理,我们采取的优化方法是三数取中,即选取三个数,对其进行排序,然后选取中间数作为中间枢纽。
int piv; int m = low + (high - low) / 2; if (L->r[low] > L->r[high]) { swap(L, low ,high); } if (L->r[m] > L->r[high]) { swap(L, high, m); } if (L->r[m] > L->r[low]) { swap(L, m, low); } piv = L->r[low];
-
优化不必要的交换
我们发现中间枢纽的值在不停的交换,实际上它只要到达它最终的位置就行。因此我们可以对其进行优化,减少不必要的交换。
int Partition(SqList *L, int low, int high) { int piv; //piv = L->r[low]; int m = low + (high - low) / 2; if (L->r[low] > L->r[high]) { swap(L, low ,high); } if (L->r[m] > L->r[high]) { swap(L, high, m); } if (L->r[m] > L->r[low]) { swap(L, m, low); } piv = L->r[low]; L->r[0] = piv;//将中间枢纽的值存储在L->r[0]中 while (low < high) { while (low < high && L->r[high] >= piv) { --high; } //swap(L, low, high); L->r[low] = L->r[high] while (low < high && L->r[low] <= piv) { ++low; } //swap(L, low, high); L->r[high] = L->r[low]; } L->r[low] = L->r[0] return low; }
-
优化排序小数组时的排序方案
对于数组元素比较少的数组,大可不必用快速排序,可以直接采用直接插入排序。
-
-
算法复杂度及稳定性分析
最好的情况下时间复杂度为O(nlogn),最坏的情况下是O(n^2)。算法是不稳定的。
3. 选择排序
1.简单选择排序
-
基本思想
通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录交换。
-
代码实现
void SelectSort(SqList *L) { int i, j, min; for (int i=1; i < L->length; i++) { min = i; for (int j = i + 1; j <= L->length; j++) { if (L->r[min] > L->r[j]) { min = j; } } if(i != min) { swap(L, i, min); } } }
-
算法复杂度和稳定性分析
最好和最坏的情况都是O(n^2)。算法是稳定的。
2.堆排序
-
基本思想
堆的定义:堆是具有以下性质的二叉树:每个节点的值都大于或者等于其左右孩子的节点的值,称为大顶堆;每个节点的值都小于或者等于其左右孩子的值,称为小顶堆。
堆排序算法思路:将待排序的序列构成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值)。然后将剩余n-1个元素重新构成一个堆,这样就可以得到n个元素中的次大值。如此反复进行,便能得到一个有序序列。
-
代码实现
//堆序列表进行堆排序 void HeapSort(SqList *L) { int i; for (int i = L->length / 2; i > 0; i--) {//先将待排序序列变成大顶堆 HeapAdjust(L, i, L->length); } for (i = L->length; i > 1; i--) { swap(L, 1, i);//交换最大元素与末尾元素的位置 HeapAdjust(L, 1, i-1);//调整堆 } } //堆调整函数 void HeapAdjust(SqList *L,int s,int m) { int j,temp; temp = L->r[s]; for (j = 2 * s;j < = m; j *= 2) { if (j <m && L->r[j] < L->r[j+1]) {//比较左右子节点的大小,取其中较大的节点 ++j; } if (temp >= L->r[j]) {//若节点大于左右子节点,则堆已经完成 break; } L->r[s] = L->r[j];//交换值 s = j; } L->r[s] = temp; }
-
算法复杂度及稳定性分析
最好的和最坏的情况都是O(n*logn)。算法是不稳定的。
4.归并排序
-
算法思路
归并排序(Merging Sort)就是利用归并的思想实现的排序方法。它的原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2| (x表示不小于x的最小整数)个长度为2或1的有序子序列:再两两归并,...如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。
-
代码实现
1.递归实现归并排序
//归并排序算法 void MergeSort(SqList *L) { MSort(L->r, L->r, 1, L->length); } //归并排序 void MSort(int SR[], int TR1[], int s, int t) { int m; int TR2[MAXSIZE + 1]; if (s == t) { TR1[s] == SR[s]; } else { m = (s + t) / 2; MSort(SR, TR2, s, m); MSort(SR, TR2, m+1, t); Merge(TR2, TR1, s, m, t); } } //合并排序算法 void Merge(int SR[], int TR[], int i, int m, int n) {//将有序的SR[i到m]和有序的SR[m到n归并为有序的TR int j, k, l; for (j = m+1, k = i;i <= m && j <= n; k++) { if (SR[j] < SR[i]) { TR[k] = SR[i++]; } else { TR[k] = SR[j++]; } } if (i <= m) { for (; i<=m; i++) { TR[K++] = SR[i]; } } if (j <= n) { for (; j<=n; j++) { TR[k++] = SR[j]; } } }
-
算法复杂度及稳定性分析
2.非递归实现归并排序
void MergeSort2(SqList *L) {//对顺序表L作归并非递归排序 int*TR = new int[L->length]; int k = 1;//将相邻距离为k的子序列合并 while (k < L->length) { MergePass(L->r, TR, k, L->length); k = 2 * k; MergePass(TR, L->r, k, L->length); k = 2 * k; } } void MergePass(int SR[], int TR[], int s, int n) {//将SR[]中相邻长度为s的子序列两两归并到TR[] int i = 1; int j; while (i <= n-2*s+1) {//两两归并 Merge(SR, TR, i, i+s-1, i+2*s-1); i = i + 2 * s; } if (i < n-s+1) {//归并最后两个子序列 Merge(SR, TR, i, i+s-1, n); } else { for(j = i; j <= n; j++) {//若最后只剩下一个子序列 TR[j] = SR[j]; } } }
在使用归并排序时,应尽量使用非递归方法。
-
算法复杂度及稳定性分析
最好和最坏的情况都是O(nlogn),辅助空间是O(n)。算法是稳定的。
LeetCode实战
1.LeetCode之215题 数组中的第k个最大元素
1.题目描述
给定整数数组 nums
和整数 k
,请返回数组中第 **k**
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素
示例1:
输入:[3,2,1,5,6,4] 和 k = 2
输出:5
提示
1 <= k <= nums.length <= 104
-104 <= nums[i] <= 104
2.解题思路及代码实现
1. 使用 [1]
-
解题思路
返回第k个选择的元素,注意这里我们每次选择的是最大的元素。
-
代码实现
/* * @lc app=leetcode.cn id=215 lang=cpp * * [215] 数组中的第K个最大元素 */ // @lc code=start class Solution { public: int findKthLargest(vector<int>& nums, int k) { int len = nums.size(); for (int i=0; i < k; i++) { int max = i; for (int j = i+1; j<len; j++) { if(nums[j] > nums[max]) { max = j; } } if (max != i) { swap(nums, i, max); } } return nums[k-1]; } void swap(vector<int> &nums, int i,int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] =temp; } }; // @lc code=end
2.使用[2]
-
解题思路
构造大顶堆,返回第k个大顶堆堆顶的元素
-
代码实现
class Solution { public: int findKthLargest(vector<int>& nums, int k) { int len = nums.size(); for (int i = len/2-1; i >= 0; i--) { HeapAdjust(nums, i, len); } int count = 0; for (int i = len; i >= 0; i--) { count++; if(count == k) return nums[0]; swap(nums, 0, i-1); HeapAdjust(nums, 0, i-1); } return 0; } void HeapAdjust(vector<int> &nums, int i,int len) { int temp = nums[i]; while (2*i+1 < len) { int max = 2*i+1; if (2*i+2 < len && nums[2*i+2] > nums[2*i+1]) { max = 2*i+2; } if (nums[max] <= temp ) { break; } nums[i] = nums[max]; i = max; } nums[i] = temp; } void swap(vector<int> &nums, int i,int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] =temp; } };
3.使用[3]
-
解题思路
在分解的过程当中,我们会对子数组进行划分,如果划分得到的 中间元素的下标 正好就是我们需要的下标,就直接返回中间元素;否则,如果 中间元素比目标下标小,就递归右子区间,否则递归左子区间。这样就可以把原来递归两个区间变成只递归一个区间,提高了时间效率。这就是「快速选择」算法。
-
代码实现
class Solution { public: int findKthLargest(vector<int>& nums, int k) { int low = 0, high = nums.size() - 1; return nums[QuickFind(nums, low, high, high - k + 1)]; } int QuickFind(vector<int> &nums, int low ,int high, int index) { int piv = Partiton(nums, low, high); if (piv == index) return piv; else { return index > piv ? QuickFind(nums, piv+1, high, index) : QuickFind(nums, low, piv-1, index); } } int Partiton(vector<int> &nums, int low,int high) { int piv; int m = low + (high - low) / 2; if (nums[low] > nums[high]) { swap(nums, low ,high); } if (nums[m] > nums[high]) { swap(nums, high, m); } if (nums[m] > nums[low]) { swap(nums, m, low); } piv = nums[low]; while(low < high) { while (low < high && nums[high] >= piv) { --high; } swap(nums, low, high); while(low < high && nums[low] <= piv) { ++low; } swap(nums, low, high); } return low; } void swap(vector<int> &nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } };
2. LeetCode之347 前K个高频元素
1.题目描述
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
示例一
输入:nums = [1,1,1,2,3],k=2
输出:[1,2]
示例二
输入: nums = [1],k=1
输出: [1]
2.解题思路及代码实现
-
解题思路
使用map统计各个数字出现的次数,然后使用桶排序对出现的次数进行排序即可.
-
代码实现
/* * @lc app=leetcode.cn id=347 lang=cpp * * [347] 前 K 个高频元素 */ // @lc code=start class Solution { public: vector<int> topKFrequent(vector<int>& nums, int k) { map<int,int> con; int max_count = 0; for (int i = 0; i<nums.size(); i++) { max_count = max(max_count, ++con[nums[i]]); } vector<vector<int>> bucket(max_count + 1); for (const auto & p : con) { bucket[p.second].push_back(p.first); } vector<int> ans; for (int i = max_count; i >= 0 && ans.size() < k; i--) { for (const auto &num : bucket[i]) { ans.push_back(num); if (ans.size() == k) break; } } return ans; } }; // @lc code=end
3.LeetCode之451根据字符出现频率排序
-
解题思路
与上面一致。
-
代码实现
/* * @lc app=leetcode.cn id=451 lang=cpp * * [451] 根据字符出现频率排序 */ // @lc code=start class Solution { public: string frequencySort(string s) { map<int, int> con; int max_count = 0; for(const auto &ch : s) { max_count = max(max_count, ++con[ch]); } vector<vector<char>> bucket(max_count + 1); for (const auto &p : con) { bucket[p.second].push_back(p.first); } string res; for (int i = max_count; i>0; i--) { for (const auto &c : bucket[i]) { for (int j = 0; j < i; j++) { res.push_back(c); } } } return res; } }; // @lc code=end
4.LeetCode之75颜色分类
-
解题思路
与上面差不多
-
代码实现
/* * @lc app=leetcode.cn id=75 lang=cpp * * [75] 颜色分类 */ // @lc code=start class Solution { public: void sortColors(vector<int>& nums) { map<int, int> con; int max_count = 0; for (int i = 0; i < nums.size(); i++) { max_count = max(max_count, ++con[nums[i]]); } int index = 0; for (int i=0; i <3; i++) { for (int j = 0; j<con[i]; j++) { nums[index] = i; ++index; } } } }; // @lc code=end