排序是一个非常贴近我们生活实际的问题,而作为程序员来说,在算法入门最开始遇到的问题应该就是这几个经典的排序,对其思想的学习和代码的实现能够让我们很快领略到算法所带来的魅力。出于学习交流的目的,接下来对七大经典的排序做出一个总结和分析。
目录
一、冒泡排序
对现实生活中水中冒泡现象的模拟。正如空气密度比水小,将值更为小的(也可以是更大的,这里假定不递减排序)数当作气泡一般慢慢从下往上冒出。以长度为N的数组为例,依次从1位置到N-1位置,比较当前位置元素和后一个位置元素的值,将值更小的元素交换到数组的右边,一趟下来就实现了将最大的元素排在了数组末尾;再从1到N-2位置重复操作,将第二大的元素排在了倒数第二的位置,周而复始实现排序。具体代码如下:
#include <bits/stdc++.h>
int* bubble_sort(int arr[], int len);
void swap(int arr[], int i, int j);
int* get_array(int max_size, int max_num);
void print_array(int arr[], int len);
int* bubble_sort(int arr[], int len) {
if(!arr || len < 2)
return arr;
for(int i = len - 1; i > 0; --i) {
for(int j = 0; j <= i; ++j) {
if(arr[j] > arr[j + 1]) {
swap(arr, j , j + 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];
}
int* get_array(int max_size, int max_num, int &len) {
len = (int)max_size * (rand() % 10 + 1);
int *arr = new int[len];
srand(time(0));
for (int i = 0; i < len; i++) {
arr[i] = rand() % max_num - rand() % max_num;
}
return arr;
}
void print_array(int arr[], int len) {
printf("[");
for(int i = 0; i < len; ++i) {
if(i != len - 1)
printf("%d, ", arr[i]);
else
printf("%d", arr[i]);
}
printf("]");
}
int main()
{
printf("--------------------------------[bubble_sort]--------------------------------\n");
int len = 0;
int *arr = get_array(10, 100, len);
printf("original:");
print_array(arr, len);
bubble_sort(arr, len);
printf("sorted after:");
print_array(arr, len);
return 0;
}
时间复杂度:O(n^2)——双重for循环
空间复杂度:O(1)——无需申请额外辅助空间
稳定性:稳定——相等时不做swap即可实现稳定
/*此外可以做一个小优化,在代码中加上一个,判断该趟遍历是否进行了交换操作的标志,可以降低最好情况的时间复杂度*/
二、选择排序
顾名思义,做出选择。即每次遍历都在未排序部分选择一个最大值或最小值并将放入当前部分的首部即可。可以理解,只需要遍历一遍所有的数,就一定能找到一个最大值或最小值。具体代码如下:
#include <bits/stdc++.h>
int* selection_sort(int arr[], int len);
void swap(int arr[], int i, int j);
int* get_array(int max_size, int max_num);
void print_array(int arr[], int len);
int* selection_sort(int arr[], int len) {
if(!arr || len < 2)
return arr;
int min_index = 0;
for(int i = 0; i < len - 1; ++i) {
min_index = i;
for(int j = i + 1; j < len; ++j) {
if(arr[j] < arr[min_index]) {
min_index = j;
}
}
if(min_index != i)
swap(arr, min_index, i);
}
}
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];
}
int* get_array(int max_size, int max_num, int &len) {
len = (int)max_size * (rand() % 10 + 1);
int *arr = new int[len];
srand(time(0));
for (int i = 0; i < len; i++) {
arr[i] = rand() % max_num - rand() % max_num;
}
return arr;
}
void print_array(int arr[], int len) {
printf("[");
for(int i = 0; i < len; ++i) {
if(i != len - 1)
printf("%d, ", arr[i]);
else
printf("%d", arr[i]);
}
printf("]\n");
}
int main()
{
printf("--------------------------------[selection_sort]--------------------------------\n");
int len = 0;
int *arr = get_array(10, 100, len);
printf("original:");
print_array(arr, len);
selection_sort(arr, len);
printf("sorted after:");
print_array(arr, len);
return 0;
}
时间复杂度:O(n^2)——双重for循环
空间复杂度:O(1)——无需申请额外辅助空间
稳定性:不稳定——如:33421 -> 43321
/*此外可以做一个小优化,每趟遍历可同时选择出最大值和最小值放到对应的位置,可做系数优化但不会改变复杂度指标*/
三、直接插入排序
直接插入排序的思想是:将待排序系列分为已排序和待排序两部分,每一步从待排序中选择一个数据插入到前面已经排好序的有序序列中,直到插完所有元素为止实现整个序列的有序。具体代码如下:
#include <bits/stdc++.h>
int* insertion_sort(int arr[], int len);
void swap(int arr[], int i, int j);
int* get_array(int max_size, int max_num);
void print_array(int arr[], int len);
int* insertion_sort(int arr[], int len) {
if(!arr || len < 2)
return arr;
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);
}
}
}
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];
}
int* get_array(int max_size, int max_num, int &len) {
len = (int)max_size * (rand() % 10 + 1);
int *arr = new int[len];
srand(time(0));
for (int i = 0; i < len; i++) {
arr[i] = rand() % max_num - rand() % max_num;
}
return arr;
}
void print_array(int arr[], int len) {
printf("[");
for(int i = 0; i < len; ++i) {
if(i != len - 1)
printf("%d, ", arr[i]);
else
printf("%d", arr[i]);
}
printf("]\n");
}
int main()
{
printf("--------------------------------[insertion_sort]--------------------------------\n");
int len = 0;
int *arr = get_array(10, 100, len);
printf("original:");
print_array(arr, len);
insertion_sort(arr, len);
printf("sorted after:");
print_array(arr, len);
return 0;
}
时间复杂度:O(n^2)——双重for循环
空间复杂度:O(1)——无需申请额外辅助空间
稳定性:稳定——相等时不做swap即可实现稳定
/*在直接插入排序的基础上实现的更具效率的shell排序提高了良好数据状况下的插入排序时间效率,详情可以参照王卓老师的视频青岛大学王卓-数据结构与算法-希尔排序*/
四、归并排序
采用分治的思想,将待排序序列拆分为两个子序列,分别实现两个子序列的排序,最后将两个子序列合并起来的时候实现整个序列的有序,对这一过程中的每个序列做这种操作,从而实现最终的排序操作。具体代码如下:
#include <bits/stdc++.h>
void merge_sort(int arr[], int L, int R);
void merge(int arr[], int L, int mid, int R);
int* get_array(int max_size, int max_num);
void print_array(int arr[], int len);
void merge_sort(int arr[], int L, int R) {
if(L == R)
return;
int mid = L + ((R - L) >> 1);
merge_sort(arr, L, mid);
merge_sort(arr, mid + 1, R);
merge(arr, L, mid, R);
}
void merge(int arr[], int L, int mid, int R) {
const int len = R - L + 1;
int temp[len];
int p = L;
int q = mid + 1;
int count = 0;
while(p <= mid && q <= R)
temp[count++] = arr[p] > arr[q] ? arr[q++] : arr[p++];
while(p <= mid)
temp[count++] = arr[p++];
while(q <= R)
temp[count++] = arr[q++];
for(int i = 0; i < len; ++i)
arr[L + i] = temp[i];
}
int* get_array(int max_size, int max_num, int &len) {
len = (int)max_size * (rand() % 10 + 1);
int *arr = new int[len];
srand(time(0));
for (int i = 0; i < len; i++) {
arr[i] = rand() % max_num - rand() % max_num;
}
return arr;
}
void print_array(int arr[], int len) {
printf("[");
for(int i = 0; i < len; ++i) {
if(i != len - 1)
printf("%d, ", arr[i]);
else
printf("%d", arr[i]);
}
printf("]\n");
}
int main()
{
printf("--------------------------------[merge_sort]--------------------------------\n");
int len = 0;
int *arr = get_array(5, 10, len);
printf("Before:");
print_array(arr, len);
merge_sort(arr, 0, len - 1);
printf("After :");
print_array(arr, len);
return 0;
}
时间复杂度:O(n*logn)——利用master公式计算递归过程时间复杂度
空间复杂度:O(n)——需要等长的temp[]辅助数组
稳定性:稳定——相等保持原序入temp[]即可
/*实现了时间复杂度从n^2到nlogn的提升,原因在于n^2算法均浪费了无效的比较行为,而merge过程将每次比较行为保留成为有序的部分,每次的比较都利用了起来*/
五、堆排序
利用堆结构的优秀特性,将排序这一过程转化为对堆结构的多次调整,每次让堆给我们选出最大值或最小值,从而降低时间复杂度,具体代码如下:
#include <bits/stdc++.h>
void heap_sort(int arr[], int len);
void heap_insert(int arr[], int index);
void heapify(int arr[], int index, int size);
void swap(int arr[], int i, int j);
int* get_array(int max_size, int max_num);
void print_array(int arr[], int len);
void heap_sort(int arr[], int len) {
if(!arr || len < 2)
return;
for(int i = 0; i < len; ++i) {
heap_insert(arr, i);
}
int size = len;
swap(arr, 0, --size);
while(size > 0) {
heapify(arr, 0, size);
size--;
swap(arr, 0, size);
}
}
void heap_insert(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 = 2 * index + 1;
int larger = 0;
while(left < size) {
larger = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
larger = arr[larger] > arr[index] ? larger : index;
if (larger == index)
break;
swap(arr, index, larger);
index = larger;
left = 2 * index + 1;
}
}
void swap(int arr[], int i, int j) {
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
int* get_array(int max_size, int max_num, int &len) {
len = (int)max_size * (rand() % 10 + 1);
int *arr = new int[len];
srand(time(0));
for (int i = 0; i < len; i++) {
arr[i] = rand() % max_num;// - rand() % max_num;
}
return arr;
}
void print_array(int arr[], int len) {
printf("[");
for(int i = 0; i < len; ++i) {
if(i != len - 1)
printf("%d, ", arr[i]);
else
printf("%d", arr[i]);
}
printf("]\n");
}
int main()
{
printf("--------------------------------[heap_sort]--------------------------------\n");
int len = 0;
int *arr = get_array(5, 10, len);
printf("Before:");
print_array(arr, len);
heap_sort(arr, len);
printf("After :");
print_array(arr, len);
return 0;
}
时间复杂度:O(n*logn)——n*调整次数(二叉树高度logn) -> n*logn
空间复杂度:O(1)——就地排序
稳定性:不稳定——相等的两个数前面的那个先被交换到后面
六、快速排序
在待排序序列中随机选取一个值(相比每次都选最后一个或者第一个避免出现最坏情况),将大于改数的元素放在左侧,大于的放在右侧;继续在左右两侧实行快速排序,最终实现整个序列的有序。具体代码如下:
#include <bits/stdc++.h>
void quick_sort(int arr[], int L, int R);
void process(int arr[], int num, int L, int R, int &min_index, int &max_index);
void swap(int arr[], int i, int j);
int* get_array(int max_size, int max_num);
void print_array(int arr[], int len);
void quick_sort(int arr[], int L, int R) {
if(L == R)
return;
else if(L < R) {
int min_index = -1;
int max_index = -1;
srand(time(0));
int num = L + rand() % (R - L);
swap(arr, num, R);
process(arr, arr[R], L, R, min_index, max_index);
quick_sort(arr, L, min_index + 1);
quick_sort(arr, max_index - 1, R);
}
}
void process(int arr[], int num, int L, int R, int &min_index, int &max_index) {
min_index = L - 1;
max_index = R;
int i = 0;
while(i < max_index) {
if(arr[i] < num) {
//printf("%d:<:%d i:%d\n", num, max_index, i);
swap(arr, i++, ++min_index);
}
else if(arr[i] > num)
swap(arr, i, --max_index);
else if(arr[i] == num)
i++;
}
}
void swap(int arr[], int i, int j) {
if(i == j)
return;
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
int* get_array(int max_size, int max_num, int &len) {
len = (int)max_size * (rand() % 2 + 1);
int *arr = new int[len];
srand(time(0));
for (int i = 0; i < len; i++) {
arr[i] = rand() % max_num;
}
return arr;
}
void print_array(int arr[], int len) {
printf("[");
for(int i = 0; i < len; ++i) {
if(i != len - 1)
printf("%d, ", arr[i]);
else
printf("%d", arr[i]);
}
printf("]\n");
}
int main()
{
printf("--------------------------------[quick_sort]--------------------------------\n");
int len = 0;
int *arr = get_array(10, 10, len);
printf("Before:");
print_array(arr, len);
quick_sort(arr, 0, len - 1);
printf("After:");
print_array(arr, len);
return 0;
}
时间复杂度:O(n*logn)——master公式计算
空间复杂度:O(1)——就地排序
稳定性:不稳定——相等的两个数前面那个作为min_index时会被交换到后面
七、基数排序(桶排序)
利用数字变化范围0-9这一特性,设置九个桶,从个位开始,将对应位置上的数放入对应的桶中,不足位补0;这样利用每一位上的大小顺序逐位传递到最高位,就能实现各个数的最终排序结果。具体代码如下:
#include <bits/stdc++.h>
using namespace std;
void radix_sort(int arr[], int len);
void radix_sort(int arr[], int L, int R, int bits);
int get_digit(int num, int index);
int max_bists(int arr[], int len);
int* get_array(int max_size, int max_num);
void print_array(int arr[], int len);
void sort_array_distance_less_k(int arr[], int k);
void radix_sort(int arr[], int len) {
//printf("%d", max_bists(arr, len));
radix_sort(arr, 0, len - 1, max_bists(arr, len));
}
int max_bists(int arr[], int len) {
int max = -0XFFFF;
int i = 0;
int res = 0;
for(i; i < len; ++i) {
if(arr[i] > max)
max = arr[i];
}
while(max != 0) {
res++;
max /= 10;
}
return res;
}
void radix_sort(int arr[], int L, int R, int bits) {
const int radix = 10;
const int len = R - L + 1;
int bucket[len];
memset(bucket, 0x00, sizeof(bucket));
for(int d = 0; d < bits; ++d) {
int counts[radix];
memset(counts, 0x00, sizeof(counts));
for(int i = L; i <= R; i++)
counts[get_digit(arr[i], d + 1)]++;
for(int i = L + 1; i <= R; i++)
counts[i] += counts[i - 1];
for(int i = R; i >= L; i--) {
int j = get_digit(arr[i], d + 1);
bucket[counts[j] - 1] = arr[i];
counts[j]--;
}
// int i, j;
// for (i = L, j = 0; i <= R; i++, j++)
// arr[i] = bucket[j];
memcpy(arr, bucket, sizeof(bucket));
}
}
int get_digit(int num, int index) {
return ((num / ((int) pow(10, index - 1))) % 10);
}
int* get_array(int max_size, int max_num, int &len) {
len = (int)max_size * (rand() % 10 + 1);
int *arr = new int[len];
srand(time(0));
for (int i = 0; i < len; i++) {
arr[i] = rand() % max_num;// - rand() % max_num;
}
return arr;
}
void print_array(int arr[], int len) {
printf("[");
for(int i = 0; i < len; ++i) {
if(i != len - 1)
printf("%d, ", arr[i]);
else
printf("%d", arr[i]);
}
printf("]\n");
}
int main()
{
printf("--------------------------------[radix_sort]--------------------------------\n");
int len = 0;
int *arr = get_array(5, 1000, len);
printf("before:");
print_array(arr, len);
radix_sort(arr, len);
printf("after");
print_array(arr, len);
return 0;
}