排序算法

排序算法

算法复杂度

算法复杂度

1.快速排序

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void quick_sort(vector<int> &nums, int l, int r) 
{ if (l + 1 >= r)
{ return; }
int first = l, last = r - 1, key = nums[first];
while (first < last){
while(first < last && nums[last] >= key)
{ --last; }
nums[first] = nums[last];
while (first < last && nums[first] <= key)
{ ++first; }
nums[last] = nums[first];
}
nums[first] = key;
quick_sort(nums, l, first);
quick_sort(nums, first + 1, r);
}

归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void merge_sort(vector<int> &nums, int l, int r, vector<int> &temp) 
{ if (l + 1 >= r)
{ return; } // divide int m = l + (r - l) / 2;
merge_sort(nums, l, m, temp);
merge_sort(nums, m, r, temp); // conquer
int p = l, q = m, i = l;
while (p < m || q < r)
{ if (q >= r || (p < m && nums[p] <= nums[q]))
{
temp[i++] = nums[p++]; }
else {
temp[i++] = nums[q++];
}
}
for (i = l; i < r; ++i) {
nums[i] = temp[i];
}
}

3.插入排序

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5。
1
2
3
4
5
6
7
8
9
10
void insertsort(vector<int>&nums,int n)
{
for(int i=0;i<n;++i)
{
for(int j=i;j>=0&&nums[j]<nums[j-1];--j)
{
swap(nums[j],nums[j-1]);
}
}
}

4.冒泡排序

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void bubble_sort(vector<int>&nums,int n)
{
for(int i=0;i<n;++i)
{
for(int j=0;j<n-i;++j)
{
if(nums[j]>nums[j+1])
{
swap(nums[j],nums[j+1]);
}
}
}
return
}

5.选择排序

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

  • 初始状态:无序区为R[1..n],有序区为空;
  • 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  • n-1趟结束,数组有序化了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void selection_sort(vector<int>&nums,int n)
{
int mid;
for(int i=0;i<n-1;++i)
{
mid=i;
for(int j=i+1;j<n;++j)
{
if(nums[j]<nums[mid])
{
mid=j;
}
}
swap(nums[mid],nums[i]);
}
}

6.一些应用

(1)TopK问题

Topk问题是在面试中常出现的一个问题,在这里也翻车过几次,特在这里总结一下。

该问题是指在大量数据中找到最大的k个数(或者是第k大的数),一般来说可以用小顶堆或者快速排序来解决

小顶堆

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
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
// 对前k个元素建成小根堆
for (int i = 0; i < k; i++) {
swim(nums, i);
}
// 剩下的元素与堆顶比较,若大于堆顶则去掉堆顶,再将其插入
for (int i = k; i < nums.size(); i++) {
if (nums[i] > nums[0]) {
swap(nums[0], nums[i]);
sink(nums, 0, k - 1);
}
}
// 结束后第k个大的数就是小根堆的堆顶
return nums[0];
}

private:
// 若v1比v2优先度高,返回true
bool priorityThan(int v1, int v2) { return v1 < v2; }

// 上浮 从下到上调整堆
void swim(vector<int>& heap, int i) {
while (i > 0 && priorityThan(heap[i], heap[(i - 1) / 2])) {
swap(heap[i], heap[(i - 1) / 2]);
i = (i - 1) / 2;
}
}

// 下沉 从下到上调整堆
void sink(vector<int>& heap, int i, int N) {
while (2 * i + 1 <= N) {
int j = 2 * i + 1;
if (j + 1 <= N && priorityThan(heap[j + 1], heap[j])) {
j++;
}
if (priorityThan(heap[i], heap[j])) {
break;
}
swap(heap[i], heap[j]);
i = j;
}
}
};

快速排序

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
class Solution {
public:
int quicksort(vector<int>& nums,int l,int r)
{
int i=l+1,j=r;
while(true)
{while(i<r&&nums[i]<=nums[l])
{
++i;
}
while(l<j&&nums[j]>=nums[l])
{
--j;
}
if(i>=j)
{
break;
}
swap(nums[i],nums[j]);
}
swap(nums[l],nums[j]);
return j;
}
int findKthLargest(vector<int>& nums, int k) {
int l = 0, r = nums.size() - 1, target = nums.size() - k;
while(l<r)
{
int mid=quicksort(nums,l,r);
if(mid==target)
{
return nums[mid];
}
else if(mid<target)
{
l=mid+1;
}
else{
r=mid-1;
}
}
return nums[l];
}
};

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!