作者: 大树先生
博客: http://blog.csdn.net/koala_tree
知乎:https://www.zhihu.com/people/dashuxiansheng
GitHub:https://github.com/KoalaTree
2018 年 4 月 16 日
经典的几大排序算法,网上各种版本代码质量层次不齐。在此想自己做个总结,一方面希望通过这次总结加深自己对几种排序算法的认识和记忆,另一方面也希望能写下来与大家分享。
每个算法力求给出普通解法和最优解法,当前部分排序算法还没有给出最优解,待后续的更新和补充。
排序算法说明
1. 算法优劣说明
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
时间复杂度: 一个算法执行所耗费的时间;
空间复杂度: 运行完一个程序所需内存的大小。
2. 排序算法总结
排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 |
|
|
|
|
稳定 |
插入排序 |
|
|
|
|
稳定 |
shell排序 |
|
|
|
|
不稳定 |
选择排序 |
|
|
|
|
不稳定 |
快速排序 |
|
|
|
|
不稳定 |
归并排序 |
|
|
|
|
稳定 |
堆排序 |
|
|
|
|
不稳定 |
计数排序 |
|
|
|
|
稳定 |
桶排序 |
|
|
|
|
稳定 |
基数排序 |
|
|
|
|
稳定 |
- n – 数据规模
- k –“桶”的个数
一、冒泡排序(Bubble Sort)
平均时间复杂度:
空间复杂度:
1. 算法描述
- (1) 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- (2) 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- (3) 针对所有的元素重复以上的步骤,除了最后一个;
- (4) 重复步骤1~3,直到排序完成。
2. C++实现
2-1:基本实现
void BubbleSort(int arr[], int n)
{
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
2-2:升级版
- 加入isSwap标志,如果某次比较没有发生交换,即说明了已经有序,后面就无须进行遍历了。
void BubbleSort(int arr[], int n)
{
for (int i = 0; i < n - 1; i++) {
bool isSwap = false;
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
isSwap = true;
}
}
if(!isSwap) return;
}
}
2-3:升级版++
- 在升级版本的基础上再增添情况:已经遍历出部分有序的序列后,那部分也不用进行遍历,也就是说:发生交换的地方之后的地方不用遍历。
- 使用current 和 last 来分别记录当前交换位置和最后一次交换位置。
void BubbleSort(int arr[], int len){
int i,temp;
//记录位置,当前所在位置current和最后发生交换的地方last
int current,last = len - 1;
while(last > 0) {
current = 0;
for(i = 0;i < last;++i){
if(arr[i] > arr[i+1]){
temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
//记录当前的位置,如果没有发生交换current值即for循环初始化的0
current = i;
}
}
//若current = 0即已经没有可以交换的元素了,即已经有序了
last = current;
}
}
3. python实现
# 冒泡排序
def bubble_sort(lists):
count = len(lists)
for i in range(0, count):
for j in range(i + 1, count):
if lists[i] > lists[j]:
lists[i], lists[j] = lists[j], lists[i]
return lists
4. 动图
二、插入排序(Insertion Sort)
插入排序的基本原理通俗的来讲就是扑克牌原理,按照从大或者从小的顺序进行排序。
平均时间复杂度:
空间复杂度:
1. 算法描述
一般来说,插入排序都采用 in-place 在数组上实现。
- (1) 从第一个元素开始,该元素可以认为已经被排序;
- (2) 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- (3) 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- (4) 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- (5) 将新元素插入到该位置后;
- (6) 重复步骤2~5。
2. C++实现
void InsertSort(int arr[],int n){
for (int i =1;i <= n;++i){
for(int j = i;j > 0;--j){
if(arr[j] < arr[j -1]){
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
}
}
}
}
3. python实现
def Insert_sort(lists):
for i in range(1, len(lists)):
key = lists[i]
j = i - 1
while j>=0 and lists[j]>key:
lists[j+1] = lists[j]
j = j - 1
lists[j+1] = key
return lists
4. 动图
三、希尔排序(Shell Sort)
希尔排序是插入排序的一种更高效的改进版本。
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。
平均时间复杂度:
空间复杂度:
1. 算法描述
- (1) 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- (2) 按增量序列个数k,对序列进行k 趟排序;
- (3) 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
2. C++实现
void ShellSort(int array[]){
int index = sizeof(array)/2;
int temp=0;
while(index>=1){
for(int i=index;i<length;i++){
for(int j=i-index;j>=0;j-=index){
if(array[j]>array[j+index]){
temp = array[j];
array[j] = array[j+index];
array[j+index]=temp;
}
}
}
index = index/2;
}
}
3. python实现
def shell_sort(list):
n = len(list)
# 初始步长
gap = round(n / 2)
while gap > 0:
for i in range(gap, n):
# 每个步长进行插入排序
temp = list[i]
j = i
# 插入排序
while j >= gap and list[j - gap] > temp:
list[j] = list[j - gap]
j -= gap
list[j] = temp
# 得到新的步长
gap = round(gap / 2)
return list
4. 动图
四、选择排序(Selection Sort)
平均时间复杂度:
空间复杂度:
1. 算法描述
- (1) 初始状态:无序区为R[1..n],有序区为空;
- (2) 第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个的新无序区;
- (3) n-1趟结束,数组有序化了。
2. C++实现
void SelectSort(int a[],int n)
{
for(int i=0; i<n-1; i++)
{
int index=i; //无序区的第一个元素
for(int j=i+1; j<n; j++)
if(a[j]<a[index]) //寻找无序区内的最小值
index=j;
if(index!=i) //把找到的最小值放到无序区的最前面
{
int tmp=a[index];
a[index]=a[i];
a[i]=tmp;
}
}
}
3. python实现
def selection_sort(list):
n=len(list)
for i in range (0,n):
min = i
for j in range(i+1,n):
if list[j]<list[min]:
min=j
if min != i:
list[min], list[i] = list[i], list[min]
return list
4. 动图
五、快速排序(Quick Sort)
平均时间复杂度:
空间复杂度:
1. 算法描述
- (1) 从数列中挑出一个元素,称为 “基准”(pivot);
- (2) 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- (3) 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
2. C++实现
void swap(int* data, int i, int j){
if(i == j) return;
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
// 分区操作
int partition(int data[], int length, int start, int end){
if(data == nullptr || length <= 0 || start < 0 || end >= length)
throw new std::exception("Invalid Parameter");
int index = rand() % (end - start + 1) + start; // 随机选择基准
swap(data, index, end);
int small = start - 1;
for(index = start; index < end; ++index){
if(data[index] < data[end]){
++small;
if(small != index)
swap(data, small, index);
}
}
++small;
swap(data, small, end);
return small;
}
void quickSort(int data[], int length, int start, int end){
if(start == end) return;
int index = partition(data, length, start, end);
if(index > start)
quickSort(data, length, start, index-1);
if(index < end)
quickSort(data, length, index+1, end);
}
3. python实现
def quick_sort(list):
less = []
pivotList = []
more = []
# 递归出口
if len(list) <= 1:
return list
else:
# 将第一个值做为基准
pivot = list[0]
for i in list:
# 比基准小的值放到less数列
if i < pivot:
less.append(i)
# 比基准大的值放到more数列
elif i > pivot:
more.append(i)
# 将和基准相同的值保存在基准数列
else:
pivotList.append(i)
# 对less数列和more数列继续进行排序
less = quick_sort(less)
more = quick_sort(more)
return less + pivotList + more
4. 动图
六、归并排序(Merge Sort)
平均时间复杂度:
空间复杂度:
归并排序是利用分治的思想去实现元素的排序。
- 分治法:将原问题分解为几个规模较小,但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解。
- 归并排序将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序,最后进行合并。
1. 算法描述
- (1) 把长度为n的输入序列分成两个长度为n/2的子序列;
- (2) 对这两个子序列分别采用归并排序;
- (3) 将两个排序好的子序列合并成一个最终的排序序列。
2. C++实现
void Merge(int arr[], int reg[], int start, int end) {
if (start >= end)return;
int len = end - start, mid = (len >> 1) + start;
//分成两部分
int start1 = start, end1 = mid;
int start2 = mid + 1, end2 = end;
//然后合并
Merge(arr, reg, start1, end1);
Merge(arr, reg, start2, end2);
int k = start;
//两个序列一一比较,哪的序列的元素小就放进reg序列里面,然后位置+1再与另一个序列原来位置的元素比较
//如此反复,可以把两个有序的序列合并成一个有序的序列
while (start1 <= end1 && start2 <= end2)
reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
//然后这里是分情况,如果arr2序列的已经全部都放进reg序列了然后跳出了循环
//那就表示arr序列还有更大的元素(一个或多个)没有放进reg序列,所以这一步就是接着放
while (start1 <= end1)
reg[k++] = arr[start1++];
//这一步和上面一样
while (start2 <= end2)
reg[k++] = arr[start2++];
//把已经有序的reg序列放回arr序列中
for (k = start; k <= end; k++)
arr[k] = reg[k];
}
void MergeSort(int arr[], const int len) {
//创建一个同样长度的序列,用于临时存放
int reg[len];
Merge(arr, reg, 0, len - 1);
}
3. python实现
- 在合并的时候,判断L和R是否为空,若其中之一如L为空,则将另外一个如R中剩余的部分添加到A的末尾.
def Merge(Left, Right):
i, j = 0, 0
results = []
while i < len(Left) and j < len(Right): # 注意这里的循环判断条件,即为不添加哨兵的方式
if Left[i] <= Right[j]:
results.append(Left[i]) # error处,注意
i += 1
else:
results.append(Right[j])
j += 1
results += Left[i:] # 这里使用Left[i:]不会报溢出错误的原因是,list切片超出范围为空:[]
results += Right[j:]
return results
def Merge_sort(lists):
if len(lists) <= 1:
return lists
mid = len(lists) // 2
left = Merge_sort(lists[:mid])
right = Merge_sort(lists[mid:])
return Merge(left, right)
4. 动图
七、堆排序(Heap Sort)
平均时间复杂度:
空间复杂度:
1. 算法描述
2. C++实现
//堆排序
void HeapSort(int arr[],int len){
int i;
//初始化堆,从最后一个父节点开始
for(i = len/2 - 1; i >= 0; --i){
Heapify(arr,i,len);
}
//从堆中的取出最大的元素再调整堆
for(i = len - 1;i > 0;--i){
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//调整成堆
Heapify(arr,0,i);
}
}
void Heapify(int arr[], int first, int end){
int father = first;
int son = father * 2 + 1;
while(son < end){
if(son + 1 < end && arr[son] < arr[son+1]) ++son;
//如果父节点大于子节点则表示调整完毕
if(arr[father] > arr[son]) break;
else {
//不然就交换父节点和子节点的元素
int temp = arr[father];
arr[father] = arr[son];
arr[son] = temp;
//父和子节点变成下一个要比较的位置
father = son;
son = 2 * father + 1;
}
}
}
3. python实现
def heap_sort(list):
# 创建最大堆
for start in range((len(list) - 2) // 2, -1, -1):
sift_down(list, start, len(list) - 1)
# 堆排序
for end in range(len(list) - 1, 0, -1):
list[0], list[end] = list[end], list[0]
sift_down(list, 0, end - 1)
return list
# 最大堆调整
def sift_down(lst, start, end):
root = start
while True:
child = 2 * root + 1
if child > end:
break
if child + 1 <= end and lst[child] < lst[child + 1]:
child += 1
if lst[root] < lst[child]:
lst[root], lst[child] = lst[child], lst[root]
root = child
else:
break
4. 动图
八、计数排序(Counting Sort)
平均时间复杂度:
空间复杂度:
1. 算法描述
- (1) 找出待排序的数组中最大和最小的元素;
- (2) 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
- (3) 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
- (4) 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
2. C++实现
//计数排序
int* countSort(int* A,int k,int n) //[0,k)范围内n个数
{
int* tmp = new int[k];
int* s = new int[n];
memset(tmp, 0, sizeof(int) * k);
for (int i = 0; i < n; i++) //原始数组中的计数
tmp[A[i]]++;
for (int i = 0; i < k - 1; i++) //记录不大于该数的数字个数
tmp[i + 1] += tmp[i];
for (int i = n - 1; i >= 0; i--) //逆序输出
s[--tmp[A[i]]] = A[i]; //计数哈希数组-1即为应当对应的秩,用原数组的数赋值
delete[] tmp;
return s;
}
3. python实现
def count_sort(list):
min = 2147483647
max = 0
# 取得最大值和最小值
for x in list:
if x < min:
min = x
if x > max:
max = x
# 创建数组C
count = [0] * (max - min +1)
for index in list:
count[index - min] += 1
index = 0
# 填值
for a in range(max - min+1):
for c in range(count[a]):
list[index] = a + min
index += 1
return list
4. 动图
九、桶排序(Bucket Sort)
平均时间复杂度:
空间复杂度:
1. 算法描述
2. C++实现
void bucketSort(vector<int>& vec)
{
int length=vec.size();
vector<int> buckets(length,0);//准备一堆桶,容器的下标即待排序数组的键值或键值经过转化后的值
//此时每个桶中都是没有放值的,所以都是0
for(int i=0;i<length;++i)
{
buckets[vec[i]]++;//把每个值放入到对应的桶中
}
int index=0;
for(int i=0;i<length;++i)
{//把值取出,空桶则直接跳过
for(int j=0;j<buckets[i];j++)
{
vec[index++]=i;
}
}
}
3. python实现
def bucket(lst):
buckets = [0] * ((max(lst) - min(lst))+1)
for i in range(len(lst)):
buckets[lst[i]-min(lst)] += 1
res=[]
for i in range(len(buckets)):
if buckets[i] != 0:
res += [i+min(lst)]*buckets[i]
return res
4. 动图
十、基数排序(Radix Sort)
平均时间复杂度:
空间复杂度:
1. 算法描述
- (1) 取得数组中的最大数,并取得位数;
- (2) arr为原始数组,从最低位开始取每个位组成radix数组;
- (3) 对radix进行计数排序(利用计数排序适用于小范围数的特点)。
2. C++实现
void countSort(vector<int>& vec,int exp)
{//计数排序
vector<int> range(10,0);
int length=vec.size();
vector<int> tmpVec(length,0);
for(int i=0;i<length;++i)
{
range[(vec[i]/exp)%10]++;
}
for(int i=1;i<range.size();++i)
{
range[i]+=range[i-1];//统计本应该出现的位置
}
for(int i=length-1;i>=0;--i)
{
tmpVec[range[(vec[i]/exp)%10]-1]=vec[i];
range[(vec[i]/exp)%10]--;
}
vec=tmpVec;
}
void radixSort(vector<int>& vec)
{
int length=vec.size();
int max=-1;
for(int i=0;i<length;++i)
{//提取出最大值
if(vec[i]>max)
max=vec[i];
}
//提取每一位并进行比较,位数不足的高位补0
for(int exp=1;max/exp>0;exp*=10)
countSort(vec,exp);
}
3. python实现
import math
def radix_sort(lists, radix=10):
k = int(math.ceil(math.log(max(lists), radix)))
bucket = [[] for i in range(radix)]
for i in range(1, k+1):
for j in lists:
bucket[j/(radix**(i-1)) % (radix**i)].append(j)
del lists[:]
for z in bucket:
lists += z
del z[:]
return lists
4. 动图
- 以上图片来自维基百科及网友总结。