大噶下午好!我又来了,今天给大家介绍一下排序,是所有的排序哦。
稳定性:将需插入的数放到合理位置,数组中其他数的顺序是否会发生变化。不变化->稳定性好,反之稳定性不好。
- 插入排序:
时间复杂度(最坏/最好/平均情况):O(N^2)/O(N)/ O(N^2)
稳定性:稳定
概述:将需插入的数插入到有序数组中,挨个比较判断是否需要交换,因为数组已经有序了,所以每个数在进行插入时最多只需要遍历数组1次。
//插入排序
void InsertSort(int* a1, int n)
{
//int i = 0;
//int j = 0;
//for (j = 1; j < n; j++)
//{
//
// for (i = j; i > 0; i--)
// {
// //将插入的数的前一个数存储进tmp
// int tmp = a1[i - 1];
// //插入的数小于前一个数
// if (a1[i] < a1[i - 1])
// {
// //交换
// a1[i - 1] = a1[i];
// a1[i] = tmp;
// }
// else
// break;
// }
//}
int i = 0;
for (i = 1; i < n; i++)
{
int end = i;
int tmp = a1[end];
while (end >= 1)
{
//判断大小,升序
if (a1[end-1] > tmp)
{
a1[end] = a1[end-1];
end--;
}
else
break;
}
a1[end] = tmp;
}
}
②冒泡排序:
时间复杂度(最坏/最好/平均情况):O(N^2)/O(N)/ O(N^2)
稳定性:稳定
概述:数组内成员从最左边开始两两相比,将 大/小(取决于递增/递减) 数放在后面,直至区间所有的数比较完毕,这样一次就可以确定好数组最后一个位置的数,然后重复上述步骤 (右区间下标-1 -> 因为上一次排序已经确定好最右边的数值了) 。
③选择排序:
时间复杂度(最坏/最好/平均情况):O(N^2)/O(N^2)/ O(N^2)
稳定性:不稳定
概述:选取数组内 最大/最小(取决于 递增/递减 排序) 的数 -> 遍历1变数字数组,然后放在 第1个/最后1个 位置,再次进行寻找值时,区间的范围可以缩小(因为上一次已经确定好了一个位置的数)。循环往复即可。
//选择排序
void SelectSort(int* a1, int n)
{
int i = 0;
//区间开始下标
int begin = 0;
//区间结束下标
int end = n - 1;
//全部排序
while (begin < end)
{
int min = begin;
int max = begin;
//先排序一次
for (i = begin+1; i < end+1; i++)
{
//确定最小值下标
if (a1[i] < a1[min])
{
min = i;
}
//确定最大值下标
if (a1[i] > a1[max])
{
max = i;
}
}
//交换
Swap(&a1[min], &a1[begin]);
//判断max是否与begin相等
if (max == begin)
{
max = min;
}
Swap(&a1[max], &a1[end]);
begin++;
end--;
}
}
- 希尔排序:
时间复杂度(最坏/最好/平均情况):O(N^2)/O(N^1.3)/ O(N*logN)~O(N^2)
稳定性:不稳定
概述:将数组分为gap组数,每组有 N/gap 数,每组数据中2个数的间隔为gap,然后将每组数据排好顺序。随后再次分组(在上一次分组的基础上组数减少,即:gap减少),然后再将每组数据排好顺序。重复以上步骤,指针gap(组数)为1排好序。
//希尔排序
void ShellSort(int* a1, int n)
{
int gap = n;
int i = 0;
int j = 0;
while (gap > 1)
{
//组数
gap = (gap / 3) + 1;
//所有组都参与进来
for (j = 0; j < gap; j++)
{
//首先将每一组排好序(其中一组的排序)
for (i = j+gap ; i < n; i += gap)
{
//插入的数值下标
int end = i;
int tmp = a1[end];
while (end > j)
{
//插入的数小于前一个数就交换
if (a1[end-gap] > tmp)
{
a1[end] = a1[end-gap];
end -= gap;
}
else
break;
}
a1[end] = tmp;
}
}
}
}
⑤快速排序:
时间复杂度(最坏/最好/平均情况):O(N^2)/O(N*logN)/O(N*logN)
(1)递归:
①左右指针:
概述:将待排区间最左边的数的下标标记为指针 mid、left ,将区间最右边的数的下标标记为指针 right。
(核心:数组[right] < 数组[mid]时停止移动,left则相反)
**单趟(蛋汤)排序:
我们先不考虑2个指针相遇情况: 首先,判断right指针是否符合核心,不符合就向左移动,直至符合核心,然后,判断left是否符合核心,若不符合,向右移动,直至符合核心,当2个指针都符合核心时,交换2个数,在此基础上继续循环。那么问题来了:什么时候结束呢?答:当2个指针相遇时 -> 当2个指针相遇时说明发生了2种情况:①left没有找到符合核心的一直在移动、②right没有找到符合核心的一直在移动。①当left一直在移动时,则说明right已经符合核心了(比数组[mid]小),当相遇时,则说明:right左边的值全部 < 数组[mid],数组[right]和数组[left]指向的数 < 数组[mid],right右边的数 >= 数组[mid],那么2个相遇位置的数与数组[mid] 交换,就是 数组[mid] 应该在的位置。②当right一直在移动时,说明:数组[left] <= 数组[mid],当2个指针相遇时,左边的数 < 数组[mid],右边的数 > 数组[mid],相遇位置 <= 数组[mid]。那么相遇位置就是 数组[mid] 应该存放的正确位置。(这里我们有一个需要注意的地方:right一定要先走,①这样才可以保证2指针相遇时指向的值一定比 数组[mid] 小 ②可以说明本次交换开始,left还未移动/left指向的数 < 数组[mid])
**整体排序:
单趟排序确定好 数组[mid] 的正确位置之后,将这个位置作为区间分割点:左区间and右区间(习惯先递归左区间)。当一个区间只有1个数/left>right时,则不需要递归(return)。
②左右指针(挖坑):与上述核心相同,只是要先将 数组[mid] 拿出去,将left/right符合核心的数放入坑中,放入坑中后产生新的坑。
//快速排序
void QuickSort(int* a1, int begin, int end)
{
if (begin >= end)
{
return;
}
int left = begin;
int right = end;
int keyi = begin;
while (left < right)
{
//让right先走,寻找比keyi小的值
while (left < right && a1[right] >= a1[keyi])
{
right--;
}
//让left走,找比keyi大的值
while (left < right && a1[left] <= a1[keyi])
{
left++;
}
Swap(&left, &right);
}
//keyi右边全部交换好,准备将keyi放入正确的位置
Swap(&a1[left], &a1[keyi]);
//keyi要起到分割的作用了
keyi = left;
//先走左边
//[begin-keyi-1] keyi [keyi+1,end]
QuickSort(a1, begin, keyi - 1);
//走右边
QuickSort(a1, keyi + 1, end);
}
③快慢指针:
**单趟(蛋汤)排序:
首先我们选取待排区间第1个数下标 key为关键点,创建2个前后指针cur、prev(cur与prev不在同一位置起点比较好写算法),cur在key的位置,prev在后面1个位置。循环内容:开始逐步移动prev,每移动1次prev就比较1次(与数组[key]比较),若 数组[prev] < 数组[key] ,将cur增加1(向后移动1位), 数组[cur] 与 数组[prev] 交换,一直循环下去直至prev超出区间范围。
循环结束后将 数组[key] 与 数组[cur] 交换,并将key更改为cur作为下一次递归的分割点。
本质上是将比数组[key]小的值推到前面,而cur在最后一定是指向小于数组[key]区间的最后1个数,这时交换数组[key]与数组[cur],可以保证数组[key]移动到正确位置。
**整体排序:
用上一次的 key 作为分割点进行递归,左区间and右区间,当区间内的 prev > end(区间最右边下标)时终止递归。
//⑧快速排序(前后指针法)
void QuickFBpSort(int* a, int begin, int end)
{
if (begin >= end)
return;
int cur = begin + 1;
int prev = begin;
int key = begin;
while (cur <= end)
{
if (a[cur] < a[key] && ++prev != cur)
{
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[key], &a[prev]);
key = prev;
QuickFBpSort(a, begin, key - 1);
QuickFBpSort(a, key+1, end);
}
(2)非递归:
总体思路:非递归需要用到栈存储待排数组 begin 和 end 的下标,找到关键点,然后遵循递归的排序顺序,将栈中的数据排空就说明排序完成。由于栈是后进先出,我们可以根据自己的需要来选择放入的下标。
具体实现方法:(需要创建1个找到关键点的函数 - KeyPoint )
将待排数组的 begin 和end (下标)放进栈中,然后从栈中pop出2个数,用 KeyPoint 找到关键点,作为区分左右区间的分割点,然后将左右区间的4个下标放入栈中 --- 往复循环,直至栈中无数据。
//⑨快速排序(非递归法)
void QuickSortkNotR(int* a, int begin, int end)
{
//创建栈(后进先出)
ST stack;
//初始化栈
Stack_init(&stack);
Stack_push(&stack, begin);
Stack_push(&stack, end);
while (Stack_empty(&stack) != NULL)
{
//区间最右边下标
int right = Stack_top(&stack);
Stack_pop(&stack);
//区间最左边下标
int left = Stack_top(&stack);
Stack_pop(&stack);
//将key放到属于它的位置,然后记录key值
int key = PartOneFBPSort(a, left, right);
//进行下一组排序
//右边
if (key + 1 < end)
{
Stack_push(&stack, key + 1);
Stack_push(&stack, end);
}
//左边
if (key - 1 > begin)
{
Stack_push(&stack, begin);
Stack_push(&stack, key - 1);
}
}
//销毁栈
Stack_destroy(&stack);
}
- 归并排序:
时间复杂度(最坏/最好/平均情况):O(N*logN)/O(N*logN)/O(N*logN)
概念:归并排序实际上是一个将有序区间组数不断缩小、区间不断扩大的排序方式。
**递归:①找到待排区间中间坐标mid作为分割点将区间分为左区间和右区间(要将mid包含进去),②随后进行递归,直到left >= right,递归终止。③进行排序
void _MargeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end)
return;
int mid = (begin + end) / 2;
_MargeSort(a, begin, mid, tmp);
_MargeSort(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 MargeSort(int* a, int begin, int end)
{
int* tmp = (int*)malloc(sizeof(int) * (end - begin + 1));
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
_MargeSort(a, begin, end, tmp);
}
**非递归:思路是相同的,都是进行分组排序,利用rangeN变量来控制组内成员个数,首先我们要将rangeN变量设置为1(组内成员个数为1),然后2组相比较/排序,直到整个数组排序完毕。然后调整rangeN (以2倍的方式调整),再重复上述步骤。这个思路需要注意的是:调整好组内成员个数时,要注意会不会越界访问,若存在越界访问问题,及时调整小组的区间 -> 调整下标。
//11.①归并排序(非递归)- 修正溢出的区间下标
void MargeSortNotR(int* a, int begin, int end)
{
int* tmp = (int*)malloc(sizeof(int) * (end - begin + 1));
int i = 0;
int rangeN = 1;
while (rangeN < end + 1)
{
int j = 0;
for (i = 0; i <= end; i += 2 * rangeN)
{
int begin1 = i, end1 = begin1 + rangeN - 1;
int begin2 = end1 + 1, end2 = begin2 + rangeN - 1;
//修正
if (end1 > end)
{
end1 = end;
begin2 = end;
end2 = end - 1;
}
else if (begin2 > end)
{
begin2 = end;
end2 = end - 1;
}
else if (end2 > end)
{
end2 = end;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[j++] = a[begin1++];
}
else
tmp[j++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
}
memcpy(a, tmp, sizeof(int) * (end - begin + 1));
rangeN *= 2;
}
}
以上就是各种排序的详情介绍了,本人已累摊。