目录
(本文排序默认排升序)
插入排序
直接插入排序
直接插入排序就像是摸牌,一个一个插入,如何保证手上的牌有序,就需要插入的牌从左往右比较,如果比插入的小就替换,直到最后一个数字插入
时间复杂度:O(N^2)
void InsertSort(int* a, int n)
{
for (int end = 1; end < n; end++)
{
int tmp = a[end];
for (int i = 0; i < end; i++)
{
if (a[end] < a[i])
{
Swap(&a[end], &a[i]);
}
}
}
}
希尔排序
希尔排序逻辑是将数分组排序,如图中一样,将间隔gap=3的数组成一组,可以将大的数更快移到右边,小的数更快移到左边。但这样并不能时数组完全有序
当gap越小时,就越接近有序,最后gap==1( gap/2 或者 gap/3+1 使得最后gap==1)时,就是插入排序,使数组完全有序
时间复杂度O(N^1.3)
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap /= 2;
for (int i = 0; i < n-gap; i++)
{
int end = i;
int tmp = a[i + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end+gap]=a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
选择排序
选择排序
1、假设数组最左边位置left为最小的数,然后去跟数组后面的数比较,如果有比这个数小的,用min记录其位置
假设数组最右边位置right为最小的数,然后去跟数组前面的数比较,如果有比这个数大的,用max记录其位置
2、走完一次循环后交换left跟min位置的数,交换right跟max位置的数,使得最左边为最小的数,最右边为最大的数
3、接着数组从left+1到right-1开始循环,找出次小次大的数,直到left与right相遇,排序结束
时间复杂度O(N^2)
void SelectSort(int* a, int n)
{
int left = 0;
int right = n - 1;
int max = left, min = right;
while (left < right)
{
min = left;
for (int i = left; i < right; i++)
{
if (a[min] > a[i])//把小的数放在左边
{
min = i;
}
if (a[max] < a[i])
{
max = i;
}
}
Swap(&a[min], &a[left]);
//left与max重叠
if (left == max)
{
max = min;
}
Swap(&a[max], &a[right]);
right--;
left++;
}
}
堆排序
1、向上调整建大堆,这样就可以把数组中最大的数放在根节点
2、把第一个数跟最后一个数交换,使数组最后的数是最大的数
3、向下调整(排好的数不需要再调整),让根节点是最大的数
循环交换和向下调整,直到所有数排好顺序
时间复杂度O(NlogN)
void Adjustup(int* a, int child)
{
while (child > 0)
{
int parent = (child - 1) / 2;
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
}
child = parent;
}
}
Adjustdown(int* a, int n)
{
int parent = 0;
int child = parent * 2 + 1;
while (child+1<n&&child>0)
{
if (a[child + 1] > a[child])//选出较大的子节点
{
child = child + 1;
}
if (a[parent] < a[child])
{
Swap(&a[parent], &a[child]);
}
parent = child;
child = parent * 2 + 1;
}
}
void HeapSort(int* a, int n)
{
//向上调整建大堆
for (int i = 0; i < n; i++)
{
Adjustup(a, i);
}
//排序
int end = n - 1;
while (end >= 0)
{
Swap(&a[0], &a[end]);
Adjustdown(a, end);
end--;
}
}
交换排序
冒泡排序
数组最开始两两比较,记录较大值的位置,往后走如果有更大的数就记录更大数的值,遍历完一遍数组后就找到最大的值。把最后一个元素的值跟最大的值交换,循环直到排好所有数
void BubbleSort(int* a, int n)
{
int end = n - 1;
while (end >= 0)
{
int tmp = 0;
for (int i = 0; i < end; i++)
{
if (a[tmp] < a[i])
{
tmp = i;
}
}
Swap(&a[tmp], &a[end]);
end--;
}
}
快速排序
Hoare快排
1、把数组第一个数位置记为key,left从key之后开始找比key位置大的数,right从数组最后往前面找比key位置小的数
2、交换left跟right位置的数,循环直到left与right相遇
3、交换key与相遇位置的值,这样key的值就放在正确的位置,前面数比key位置数小,后面数比key位置数小
4、最后分别递归key之前跟key之后的数,递归返回的条件是left>=right
注意:这里有一个细节,怎样使得相遇时的点比key的值要小呢?如果让left先走,但是当没有找到大的数就相遇,相遇时right大小就并不能确定
让right先走找小的值就可以解决(让right作为key时,让left先走,就能让相遇时的值比key要大)
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return NULL;
}
int begain = left;
int end = right;
int key = left;
while (left < right)
{
while (left < right && a[right] >= a[key])//右边找小
right--;
while (left < right && a[left] <= a[key])//左边找大
left++;
Swap(&a[left], &a[right]);
}
Swap(&a[key], &a[left]);
key = left;
//
QuickSort(a, begain, key - 1);
QuickSort(a, key + 1, end);
}
快排挖坑法
与上一个类似,不是交换数据,而是形成坑位
单趟
1、将第一个作为key,形成坑位
2、让右边找小值放到坑位中,自己形成新的坑位
3、让左边找大值放到坑位中,自己形成新的坑位
4、左右相遇时,所在的位置是一个坑位,把key放到坑位中
int PartSort2(int* a, int left, int right)
{
int mid = GetMidNum(a, left, right);//三数取中
if (mid != left)
{
Swap(&a[left], &a[mid]);
}
int key =a[left];
int begain = left;
int end = right;
while (left < right)
{
while (left < right && a[right] >= key)
{
right--;
}
a[left] = a[right];
while (left < right && a[left] <= key)
{
left++;
}
a[right] = a[left];
}
a[left] = key;
int keyi = left;
return keyi;
}
前后指针快排
单趟
1、定义两个指针如图所示,cur在prev前一个位置
2、cur往后找小于key的值,因为cur之前的数都是比key要大的,所以直接与++prev交换
3、当cur越界之后,prev所在的位置就是比key小的数,将key与prev的数交换
int PartSort3(int* a, int left, int right)
{
int mid = GetMidNum(a, left, right);//三数取中
if (mid != left)
{
Swap(&a[left], &a[mid]);
}
int prev = left;
int cur = left + 1;
int keyi = left;
while (cur <= right)
{
if(a[cur] < a[keyi])//cur找小
{
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
return keyi;
}
最开始的Hoare快排有一个问题,当数组为有序时 ,就会对有序数组重复遍历,时间复杂度会达到O(N^2)
有两种方法可以解决这个问题
1、随机key
用rand生成[left,right]之间的随机数
left + (rand() % (right - left));这部要加left才能保证生成的随机数在[left,right]范围内
int randi =left + (rand() % (right - left));
Swap(&a[left], &a[randi]);
2、三数取中
在最左边、中间、最右边三个数中找到一个中等大小的值,然后与数组最左边的数交换
int GetMidNum(int* a, int left, int right)
{
//找出中等的数
int mid = (right - left) / 2;
if (a[right] > a[left])
{
if (a[mid] > a[right])
{
return right;
}
else if (a[left] > a[mid])
{
return left;
}
else
{
return mid;
}
}
else
{
if (a[mid] > a[left])
{
return left;
}
else if (a[right] > a[mid])
{
return right;
}
else
{
return mid;
}
}
}
归并排序
1、递归分解数组到两个之间比较,把小的先拷贝到新数组tmp中,再把大的拷到tmp中,然后把tmp中的数据拷贝回原数组
2、两个与两个的区间比较,先拷贝小的数,然后拷贝大的数到tmp,把tmp数据拷贝回原数组
3、递归回到数组分成两个区间比较之后完成排序
注:两个区间比较完之后,可能有一个区间的数没有拷贝到tmp中,还需要写两个while循环来把没有拷贝完的拷贝到tmp
void _MergeSort(int* a, int* tmp, int begain, int end)
{
if (begain >= end)
{
return;
}
int mid = (begain + end) / 2;
//区间 [begain,mid][mid+1,end]
int begain1 = begain, end1 = mid;
int begain2 = mid + 1, end2 = end;
_MergeSort(a, tmp, begain1, end1);
_MergeSort(a, tmp, begain2, end2);
int i = begain;
while (begain1 <= end1 && begain2 <= end2)
{
if (a[begain1] > a[begain2])
{
tmp[i++] = a[begain2++];
}
else
{
tmp[i++] = a[begain1++];
}
}
while (begain1 <= end1)
{
tmp[i++] = a[begain1++];
}
while (begain2 <= end2)
{
tmp[i++] = a[begain2++];
}
memcpy(a + begain, tmp + begain, sizeof(int) * (end-begain+1));
}
void MergeSort(int* a, int n)
{
int tmp = (int)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
}
_MergeSort(a, tmp, 0, n - 1);
}
归并非递归
时间复杂度O(NlogN)
归并思想是两两区间归并,定义四个变量来控制这两个区间,[begain1,end1][begain2,end2]
非递归相比于递归方式难点在于区间的控制,画图的方式能够更好理解
易错点:归并后拷贝回去是,begain1已经改变,i记录的是每次归并前begain的初始位置,
void MergeSortNonR(int* a, int begain, int end, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
int j = i;
int begain1 = i, end1 = i + gap - 1;
int begain2 = i + gap, end2 = i + 2*gap - 1;
if (end1 > n-1 || begain2 > n-1)
{
break;
}
if (end2 > n-1)
{
end2 = n-1;
}
while (begain1 <= end1 && begain2 <= end2)
{
if (a[begain1] > a[begain2])
{
tmp[j++] = a[begain2++];
}
else
{
tmp[j++] = a[begain1++];
}
}
while (begain1 <= end1)
{
tmp[j++] = a[begain1++];
}
while (begain2 <= end2)
{
tmp[j++] = a[begain2++];
}
memcpy(a + i, tmp + i, sizeof(int) * (end2-i+1));//i的位置就是begain的初始位置
}
gap *= 2;
}
free(tmp);
tmp = NULL;
}
非比较排序
计数排序
用一个数组tmp记录排序数组中元素出现的次数,该数组大小就是排序数组最大值减最小值
tmp中的数组元素加min就是排序数组中的元素 ,之后只需要将tmp中的数放到排序数组
计数排序只适合在元素较集中、整形的数组,不适合分散数组跟整形数组
void CountSort(int* a, int n)
{
int max = a[0], min = a[0];
int i = 1;
for (int i = 1; i < n; i++)//记录最大 最小值
{
if (a[i] > max)
{
max = a[i];
}
if (a[i] < min)
{
min = a[i];
}
}
int range = max - min;
int* tmp = (int*)malloc(sizeof(int) * range);
if (tmp == NULL)
{
perror("malloc fail");
return ;
}
memset(tmp, 0, sizeof(int)*range);
for(int i=0;i<n;i++)
{
tmp[a[i]-min]++;
}
int j = 0;
for (int i = 0; i < range-1; i++)//排序
{
while (tmp[i]--)
{
a[j++] = i + min;
}
}
}