排序是我们数据结构中不可或缺的算法,也是面试官HR最爱问的问题之一,所以要清楚排序就要明白排序的核心思想是什么,什么样的排序时间和空间复杂度更低,什么情况下用什么样的排序会更优。
最常见的就是十大经典排序:冒泡排序、选择排序、堆排序、插入排序、希尔排序、快速排序、归并排序、基数排序、计数排序和桶排序。
冒泡排序
算法思想:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
void BubbleSort(int* a, int n) {
for (int i = 0; i < n - 1; i++) {
bool exchange = false;
for (int j = 1; j < n - i; j++) {
if (a[j-1] > a[j]) {
int temp = a[j];
a[j] = a[j -1];
a[j - 1] = temp;
exchange = true;
}
}
if (exchange == false) {
break;
}
}
}
选择排序
算法思想:
- 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
- 以此类推,直到所有元素均排序完毕
void Swap(int* p1, int* p2) {
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void SelectSort(int* a, int n) {
int begin = 0, end = n - 1;
while (begin < end) {
int maxi = begin, mini = begin;
for (int i = begin; i <= end; i++) {
if (a[i] > a[maxi]) {
maxi = i;
}
if (a[i] < a[mini]) {
mini = i;
}
}
Swap(&a[begin], &a[mini]);
//如果maxi和begin重叠,修正一下即可
if (begin == maxi) {
maxi = mini;
}
Swap(&a[end], &a[maxi]);
++begin;
--end;
}
}
堆排序
算法思想:
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
void Adjustdown(int*a,int n,int parent) {
int child = parent * 2 + 1;
while (child < n) {
//找出小的那个孩子
if (a[child + 1] < n && a[child + 1] > a[child]) {
child++;
}
if (a[child] > a[parent]) {
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else {
break;
}
}
}
//排升序
void HeapSort(int* a, int n) {
//建大堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
Adjustdown(a,n,i);
}
int end = n - 1;
while (end > 0) {
Swap(&a[0], &a[end]);
Adjustdown(a, end, 0);
--end;
}
}
插入排序
算法思想:
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
- 重复步骤2~5
void InsertSort(int* a, int n) {
for (int i = 0; i < n-1; i++) {
//[0,end]有序,插入tmp依旧有序
int end = i;
int tmp = a[i+1];
while (end >= 0) {
if (a[end] > tmp) {
a[end + 1] = a[end];
--end;
}
else {
break;
}
}
a[end+1] = tmp;
}
}
希尔排序
算法思想:
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
//希尔排序:1.预排序--接近有序 2.插入排序
void ShellSort(int* a, int n) {
int gap = n; //归纳公式:合计gap组,每组n/gap个
while (gap > 1) {
//gap = gap / 3+1;
gap = gap / 2;
for (int i = 0; i < n - gap; i++) {
int end = i;
int tmp = a[end + gap];
while (end >= 0) {
if (a[end] > tmp) {
a[end + gap] = a[end];
end -= gap;
}
else {
break;
}
}
a[end + gap] = tmp;
}
}
}
快速排序
算法思想:
- 选取第一个数为基准
- 将比基准小的数交换到前面,比基准大的数交换到后面
- 对左右区间重复第二步,直到各区间只有一个数
//三数取中(针对有序)
int GetMidIndex(int* a, int left, int right) {
int mid = (left + right) / 2;
//int mid = left + (rand() % (right - left)); //针对全是一样的
if (a[left] < a[mid]) {
if (a[mid] < a[right]) {//a[left] < a[mid] < a[right]
return mid;
}
else if (a[right] < a[left]) {// a[right] < a[left] < a[mid]
return left;
}
else {
return right;
}
}
else {// a[left] > a[mid]
if (a[mid] >a[right]) {//a[left] > a[mid] > a[right]
return mid;
}
else if (a[right] > a[left]) {//a[right] > a[left] > a[mid]
return left;
}
else {
return right;
}
}
}
//hoare(左右指针法)
//[left,right]
// L、R相遇:R先走,R停下来的位置小于等于key;L先走,L停下来的位置大于等于key
int PartSort(int* a, int left, int right) {
int midi = GetMidIndex(a, left, right);
Swap(&a[left], &a[midi]);
int keyi = left;//左边做key,右边先走,保障了相遇位置的值小于等于key
//int keyi=right; //右边做key,左边先走,保障了相遇位置的值大于等于key
while (left < right) {
//右边找小
while (left < right && a[right] >= a[keyi])
right--;
//左边找大
while (left < right && a[left] <= a[keyi])
left++;
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
return left;
}
//挖坑法
int PartSort2(int* a, int left, int right) {
int midi = GetMidIndex(a, left, right);
Swap(&a[left], &a[midi]);
int key = a[left];
int hole = left;
while (left < right) {
//右边找小
while (left < right && a[right] >= key)
right--;
a[hole] = a[right];
hole = right;
//左边找大
while (left < right && a[left] <= key)
left++;
a[hole] = a[left];
hole = left;
}
a[hole] = key;
return hole;
}
//前后指针法
//1.最开始prev和cur相邻
//2.当cur遇到比key大的值以后,他们之间的值都是比key大的值
//3.cur找小,找到小的以后跟++prev位置的值交换,相当于大翻滚式的往右推同时把小的换到左边
int PartSort3(int* a, int left, int right) {
int midi = GetMidIndex(a, left, right);
Swap(&a[left], &a[midi]);
int prev = left;
int keyi = left;
int cur = left + 1;
while (cur <= right) {
if (a[cur] < a[keyi] && ++prev != cur) {
//++prev;
Swap(&a[prev], &a[cur]);
}
++cur;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
return keyi;
}
void QuickSort(int* a, int begin, int end) {
if (begin>=end) {
return;
}
//int keyi = PartSort(a, begin, end);
//int keyi = PartSort2(a, begin, end);
int keyi = PartSort3(a, begin, end);//分割区间
// [begin,kryi-1] keyi [keyi+1,end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi+1, end);
}
归并排序
算法思想:
1.把长度为n的输入序列分成两个长度为n/2的子序列;
2. 对这两个子序列分别采用归并排序;
3. 将两个排序好的子序列合并成一个最终的排序序列。
void _MergeSort(int* a, int begin, int end,int* tmp) {
if (begin == end)
return;
//小区间优化
if (end - begin + 1 < 10) {
InsertSort(a+begin, end - begin + 1);
return;
}
int mid = (begin + end) / 2;
//[begin,mid][mid+1,end]
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid+1, end, tmp);
//归并两个区间
int begin1 = begin, end1 = mid;
int begin2 = mid+1, end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2) {
if (a[begin1] <= a[begin2]) {
tmp[i++] = a[begin1++];
}
else {
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1) {
tmp[i++] = a[begin1++];
}
while (begin2 <= end2) {
tmp[i++] = a[begin2++];
}
memcpy(a+begin, tmp+ begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n) {
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL) {
perror("malloc fail\n");
return;
}
_MergeSort(a, 0, n - 1,tmp);
free(tmp);
}
基数排序
算法思想:
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点)
int maxbit(int data[], int n) //辅助函数,求数据的最大位数 {
int maxData = data[0];
///< 最大数 /// 先求出最大数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。
for (int i = 1; i < n; ++i) {
if (maxData < data[i])
maxData = data[i];
}
int d = 1;
int p = 10;
while (maxData >= p) {
//p *= 10;
// Maybe overflow maxData /= 10; ++d;
}
return d;
void radixsort(int data[], int n) //基数排序 {
int d = maxbit(data, n);
int *tmp = new int[n];
int *count = new int[10]; //计数器
int i, j, k;
int radix = 1;
for(i = 1; i <= d; i++) //进行d次排序 {
for(j = 0; j < 10; j++)
count[j] = 0; //每次分配前清空计数器
for(j = 0; j < n; j++) {
k = (data[j] / radix) % 10; //统计每个桶中的记录数
count[k]++;
}
for(j = 1; j < 10; j++)
count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中 {
k = (data[j] / radix) % 10;
tmp[count[k] - 1] = data[j]; count[k]--;
}
for(j = 0; j < n; j++) //将临时数组的内容复制到data中
data[j] = tmp[j];
radix = radix * 10;
}
delete []tmp;
delete []count;
}
计数排序
算法思想:
- 找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项;
- 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加);
- 向填充目标数组:将每个元素 i 放在新数组的第 C[i] 项,每放一个元素就将 C[i] 减去 1;
void CountSort(int* a, int n) {
int min = a[0], max = a[0];
for (int i = 0; i < n; i++) {
if (a[i] < min) {
min = a[i];
}
if (a[i] > max) {
max = a[i];
}
}
int range = max - min + 1;
int* countA = (int*)malloc(sizeof(int) * range);
memset(countA, 0, sizeof(int) * range);
//统计次数
for (int i = 0; i < n; i++) {
countA[a[i] - min]++;
}
//排序
int k = 0;
for (int j = 0; j < range; j++) {
while (countA[j]--) {
a[k++] = j + min;
}
}
}
桶排序
算法思想:
- 设置一个定量的数组当作空桶子。
- 寻访序列,并且把项目一个一个放到对应的桶子去。
- 对每个不是空的桶子进行排序。
- 从不是空的桶子里把项目再放回原来的序列中。
function bucketSort(arr, bucketSize) {
if (arr.length === 0) {
return arr;
}
var i;
var minValue = arr[0];
var maxValue = arr[0];
for (i = 1; i < arr.length; i++) {
if (arr[i] < minValue) {
minValue = arr[i]; // 输入数据的最小值
}
else if (arr[i] > maxValue) {
maxValue = arr[i]; // 输入数据的最大值
}
}
// 桶的初始化
var DEFAULT_BUCKET_SIZE = 5; // 设置桶的默认数量为5
bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
var buckets = new Array(bucketCount);
for (i = 0; i < buckets.length; i++) {
buckets[i] = [];
}
// 利用映射函数将数据分配到各个桶中
for (i = 0; i < arr.length; i++) {
buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
}
arr.length = 0;
for (i = 0; i < buckets.length; i++) {
insertionSort(buckets[i]); // 对每个桶进行排序,这里使用了插入排序
for (var j = 0; j < buckets[i].length; j++) {
arr.push(buckets[i][j]);
}
}
return arr;
}
十大经典排序算法之所以经典,自然有它的道理。如果觉得这篇文章对你有帮助可以收藏下来,也欢迎大家进行批评指正,理解排序算法可以帮助我们更加高效的编写程序,一起加油