前言
排序算法,是将一个给定的无序数字数组元素按从小到大的顺序进行排序,得到一个有序数组的过程。排序算法种类丰富,下面总结常见的几种排序算法。
话不多说,先来直观感受一下有毒的排序算法视频:http://www.bilibili.com/video/av685670?share_medium=android&share_source=copy_link&bbid=XY59BB55FA50B46679DEEE1CE8DB9A2D3BC6C&ts=1551879120053
冒泡排序
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。
这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
冒泡排序算法的原理如下:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
示例如下:
第一趟遍历索引0~7。首先比较
a
r
r
[
0
]
>
a
r
r
[
1
]
arr[0]>arr[1]
arr[0]>arr[1],则交换
a
r
r
[
0
]
arr[0]
arr[0]和
a
r
r
[
1
]
arr[1]
arr[1]元素的位置,然后
a
r
r
[
1
]
>
a
r
r
[
2
]
arr[1]>arr[2]
arr[1]>arr[2],继续交换,
6
=
a
r
r
[
2
]
<
a
r
r
[
3
]
=
8
6=arr[2]<arr[3]=8
6=arr[2]<arr[3]=8,不需要交换,6就“冒泡”到位置2不动了,接下来继续两两比较,发现8是个比较大的数,两两比较过程中会一直交换位置直到8“冒泡”到最大的位置,也表明8是这个数组最大的元素;
第二趟遍历索引0~6,索引7的元素已经是最大的不用动。重复上述比较、交换位置的过程,会找出次最大值填放在索引6位置处;
就这样经历七趟就完成了数组的排序过程
数组arr索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
原始数组arr | 6 | 1 | 5 | 8 | 2 | 3 | 5 | 2 |
第1趟 | 1 | 5 | 6 | 2 | 3 | 5 | 2 | 8 |
第2趟 | 1 | 5 | 2 | 3 | 5 | 2 | 6 | 8 |
… | ||||||||
第7趟 | 1 | 2 | 2 | 3 | 5 | 5 | 6 | 8 |
冒泡排序算法的时间复杂度
O
(
n
2
)
O(n^2)
O(n2),空间复杂度为
O
(
1
)
O(1)
O(1)。
C++示例代码
#ifndef SORT_H
#define SORT_H
#include <iostream>
using namespace std;
class Sort {
public:
void bubbleSort(int arr[],int len) {
if (arr == NULL || len < 2) {
return;
}
for (int e = len - 1; e > 0; e--) {
for (int i = 0; i < e; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
void swap(int arr[], int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
void printArray(int arr[],int len) {
if (arr == NULL) {
return;
}
for (int i = 0; i < len; i++) {
cout<<arr[i] <<" ";
}
cout << endl;
}
};
#endif
#include "sort.h"
void main()
{
int arr[] = { 6, 1, 5, 8, 2, 3, 5, 2 };
Sort mySort;
int len = sizeof(arr) / sizeof(int);
mySort.printArray(arr,len);
mySort.bubbleSort(arr,len);
mySort.printArray(arr,len);
cin.get();
}
插入排序
插入排序(Insertion Sort)的思想是把一个额外元素插入到一个有序序列的正确位置,这个排序的过程有点像打扑克牌时理牌的过程。
插入排序算法的原理如下:
- 序列第一个元素被视为已经有序的子序列
- 将有序子序列后面的元素与子序列的元素依次比较,每个元素放在其不大于的子序列某个元素后面
- 重新构成的子序列依然有序,重复步骤2
- 直到整个序列有序
示例如下:
第一趟插入
a
r
r
[
1
]
arr[1]
arr[1],比较
a
r
r
[
0
]
>
a
r
r
[
1
]
arr[0]>arr[1]
arr[0]>arr[1],则交换
a
r
r
[
0
]
arr[0]
arr[0]和
a
r
r
[
1
]
arr[1]
arr[1]元素的位置,插入完成,子序列索引0~1有序;
第二趟插入
a
r
r
[
2
]
arr[2]
arr[2],交换
a
r
r
[
1
]
arr[1]
arr[1]和
a
r
r
[
2
]
arr[2]
arr[2]元素的位置,插入完成,子序列索引0~2有序;
就这样经历七趟就完成了数组的排序过程
数组arr索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
原始数组arr | 6 | 1 | 5 | 8 | 2 | 3 | 5 | 2 |
第1趟 | 1 | 6 | 5 | 8 | 2 | 3 | 5 | 2 |
第2趟 | 1 | 5 | 6 | 8 | 2 | 3 | 5 | 2 |
… | ||||||||
第7趟 | 1 | 2 | 2 | 3 | 5 | 5 | 6 | 8 |
插入排序算法的时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( 1 ) O(1) O(1)。
C++示例代码
void insertionSort(int arr[],int len) {
if (arr == NULL || len < 2) {
return;
}
for (int i = 1; i < len; i++) {
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
选择排序
选择排序(Selection Sort)的思想就是每次遍历都找出一个全局最小值,并将该最小值提到序列的最前面(或者找出最大值放在序列最后面),然后缩小全局搜索的范围继续搜索,直到序列全部有序。
示例如下:
第一趟:搜索0-7范围内最小值为
a
r
r
[
1
]
=
1
arr[1]=1
arr[1]=1,则交换
a
r
r
[
0
]
arr[0]
arr[0]和
a
r
r
[
1
]
arr[1]
arr[1]元素的位置;
第二趟:搜索1-7范围内最小值为
a
r
r
[
4
]
=
2
arr[4]=2
arr[4]=2,则交换
a
r
r
[
1
]
arr[1]
arr[1]和
a
r
r
[
4
]
arr[4]
arr[4]元素的位置,0~2有序;
就这样经历七趟就完成了数组的排序过程
数组arr索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
原始数组arr | 6 | 1 | 5 | 8 | 2 | 3 | 5 | 2 |
第1趟 | 1 | 6 | 5 | 8 | 2 | 3 | 5 | 2 |
第2趟 | 1 | 2 | 5 | 8 | 6 | 3 | 5 | 2 |
… | ||||||||
第7趟 | 1 | 2 | 2 | 3 | 5 | 5 | 6 | 8 |
选择排序算法的时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( 1 ) O(1) O(1)。
C++示例代码
void selectionSort(int arr[],int len) {
if (arr == NULL || len < 2) {
return;
}
for (int i = 0; i < len - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < len; j++) {
minIndex = (arr[j] < arr[minIndex]) ? j : minIndex;
}
swap(arr, i, minIndex);
}
}
void swap(int arr[], int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
归并排序
归并排序(Merge Sort)使用了递归的思想进行流程设计,首先把这个序列从正中间一分为二,分别称之为左序列和右序列,并假设左右两个序列已经排好序了,那么我们只需要把左右两个有序序列合并成一个序列就好了,合并两个有序序列采用外排的方法:从左到右比较两个顺列的元素,哪个元素小哪个放在左边,直到全部排序完成。这样大数组就排序好了,但是怎么才能保证左右两个数组有序呢?答案是采用上述方法对左右两个序列也进行归并排序,如此左右划分下去直到子序列的左右序列只有一个元素为止。
示例如下:
归并排序由于采用递归的思想,说起来比较绕口,可结合下面的示例理解。
第一次划分:左序列索引0-3,右序列4-7;
第二次划分:由于左序列无序,无法进行合并操作,先对左序列做归并排序:子左序列索引0-1,子右序列2-3;
第三次划分:左序列:0,右序列1,不能继续划分,开始合并;
第一次合并
数组arr索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
原始数组arr | 6 | 1 | 5 | 8 | 2 | 3 | 5 | 2 |
第1划分 | (6 | 1 | 5 | 8 ) | ( 2 | 3 | 5 | 2) |
第2划分 | ((6 | 1 ) | (5 | 8 )) | (( 2 | 3 ) | (5 | 2)) |
第1次合并 | ((1 | 6 ) | (5 | 8 )) | (( 2 | 3 ) | (2 | 5)) |
第2次合并 | (1 | 5 | 6 | 8 ) | ( 2 | 2 | 3 | 5) |
第三次合并 | 1 | 2 | 2 | 3 | 5 | 5 | 6 | 8 |
选择排序算法的时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( n ) O(n) O(n)。
void mergeSort(int arr[],int len) {
if (arr == NULL || len < 2) {
return;
}
mergeSort(arr, 0, len - 1);
}
void mergeSort(int arr[], int l, int r) {
if (l == r) {
return;
}
int mid = l + ((r - l) >> 1);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
void merge(int arr[], int l, int m, int r) {
int* help = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = m + 1;
while (p1 <= m && p2 <= r) {
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < r - l + 1; i++) {
arr[l + i] = help[i];
}
delete[] help;
}
快速排序
快速排序(Quick Sort)是一种比较高效的排序方法,它的思想是选取序列中的一个数作为基准,将小于基准的元素全部放在数组左边,大于基准的元素全部放在数组的右边,等于基准的元素放在中间,这样就把原数组分成了左右两个部分,然后对每个部分采用同样的策略递归下去,直到整个数组有序。基准值一般选择待排序列的最后一个元素,但是为了避免最差情况,也可以优化为随机选取一个数作为基准,这时就称之为随机快速排序。
示例如下:
快速排序同样采用递归的思想,结合下面的示例理解。
第一次划分:以第八个元素2作为划分基准左边部分只有一个1,不需要排序,右边部分索引3-7;
第二次划分:由于索引3-7上8为最大值,所以划分后右部分为空,左部分索引3-6;
第三次划分:同理,左部分索引3-5;
第四次划分:左部分只有一个3,划分结束,同时排序完成。
可见随机快排还是有必要的。
数组arr索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
原始数组arr | 6 | 1 | 5 | 8 | 2 | 3 | 5 | 2 |
第1划分 | 1 | 2 | 2 | 5 | 3 | 5 | 6 | 8 |
第2划分 | 1 | 2 | 2 | 5 | 3 | 5 | 6 | 8 |
第3划分 | 1 | 2 | 2 | 5 | 3 | 5 | 6 | 8 |
第4划分 | 1 | 2 | 2 | 3 | 5 | 5 | 6 | 8 |
最终 | 1 | 2 | 2 | 3 | 5 | 5 | 6 | 8 |
选择排序算法的时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( l o g n ) O(logn) O(logn)。
void quickSort(int arr[],int len) {
if (arr == NULL || len < 2) {
return;
}
quickSort(arr, 0, len - 1);
}
void quickSort(int arr[], int l, int r) {
if (l < r) {
swap(arr, l + (int)((double)rand() / RAND_MAX * (r - l + 1)), r);
int *p=partition(arr, l, r);
quickSort(arr, l, p[0] - 1);
quickSort(arr, p[1] + 1, r);
}
}
int* partition(int arr[], int l, int r) {
int less = l - 1;
int more = r;
while (l < more) {
if (arr[l] < arr[r]) {
swap(arr, ++less, l++);
}
else if (arr[l] > arr[r]) {
swap(arr, --more, l);
}
else {
l++;
}
}
swap(arr, more, r);
return new int[2] { less + 1, more };
}
堆排序
堆排序(Heap Sort)利用的是一种特殊的完全二叉树结构–大根堆 这种数据结构的一个特征:大根堆最上面元素为最大元素。于是堆排序的调整策略就变成:首先把一个数组变为大根堆;其次把最大元素与数组最后一个元素交换;将除去最后一个元素的剩余数组调整成大根堆,重复上述操作,数组就排好序了。
示例如下:
第一步:调整为大根堆;
第二步:取最大元素后继续调整为大根堆
多次重复上述过程,排序完成。
数组arr索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
原始数组arr | 6 | 1 | 5 | 8 | 2 | 3 | 5 | 2 |
第1步 | 8 | 6 | 5 | 2 | 2 | 3 | 5 | 1 |
取最大值 | 1 | 6 | 5 | 2 | 2 | 3 | 5 | 8 |
第二步 | 6 | 2 | 5 | 1 | 2 | 3 | 5 | 8 |
… | ||||||||
最终 | 1 | 2 | 2 | 3 | 5 | 5 | 6 | 8 |
选择排序算法的时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( 1 ) O(1) O(1)。
void heapSort(int arr[],int len) {
if (arr == NULL || len < 2) {
return;
}
for (int i = 0; i < len; i++) {
heapInsert(arr, i);
}
int size = len;
swap(arr, 0, --size);
while (size > 0) {
heapify(arr, 0, size);
swap(arr, 0, --size);
}
}
void heapInsert(int arr[], int index) {
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
void heapify(int arr[], int index, int size) {
int left = index * 2 + 1;
while (left < size) {
int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
break;
}
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
桶排序
简单介绍一下桶排序的思想:假设有一亿个数,数值范围保持在0–99之间,要对这样的数组进行排数,可以通过统计0–99这一百个数各出现了多少次,然后再按0–99的顺序对原数组进行复写,每个数统计到了几次就写几个,这样数组就排好了。这一百个数称之为一百个“桶”,用来计数。
桶排序算法的时间复杂度
O
(
n
)
O(n)
O(n),空间复杂度为
O
(
n
)
O(n)
O(n)。
其他排序
待续,。。。