排序
(重点:快排,堆排,希尔,归并排序)以及它们的时间复杂度
插入排序
- **直接插入排序(**洗牌,对已经有序的元素,进来新的时候进行排序,适合接近有序,数量较小)-------O(N方)
5 4 3 2 1
5| 4 3 2 1(第一个数看做有序)
4 5| 3 2 1,插入一个数后任然有序
3 4 5| 2 1
2 3 4 5| 1
1 2 3 4 5
时间复杂度:最多搬移n-1次 ,最多比较n-1次,
稳定性:稳定,相邻位置排完序,相对顺序不变,变化不是很明显,就是稳定
核心:要插入的时候,前面已经是排好序的了,先把这个要 插入的数给“拿走”----保存起来。然后和它的前一个数值比较,搬移,在和前前一个比较…
代码实现:
void InsertSort(int *arr, int n)///
{
int i = 0;
for (; i<n - 1; i++) //n-1的原因
{
int end =i ;
int tmp = arr[end + 1];
while (end >= 0 && arr[end] > tmp) //end越界问题
{
arr[end + 1] = arr[end];
--end;
}
arr[end + 1] = tmp;
}
}
-
希尔排序---------数据量大,数据凌乱,不稳定
-
思想n个数字,用力个增量进行分组,每个组单独进行排序,然后缩小增量,直至增量为1,排序,(百度一下)
第一次分组排序后,大的尽量往后走,小的尽量往前走(gap = 3)-------接近有序
第二次减小gap在分组,以上面的同样方法(gap = 2)--------越来越接近有序
… (gap = 1)-----------有序
gap的取值最好是gap/3+1
代码实现:
void ShellSort(int * arr,int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1; //为什么是/3+1,+1保证 它 最后一次为1,取多少合适;
int i = 0;
for (; i<n - gap; ++i) //按道理i应该 是i+gap,但是不需要,因为i++之后进入了第二组
{
//为什么是n-gap?直接插入排序gap = 1;
int end = i;
int tmp = arr[end + gap];
while (end>0 && arr[end]>tmp)
{
arr[end + gap] = arr[end];
end -= gap;
}
arr[end + gap] = tmp;
}
}
}
测试希尔排序和直接插入排序。来检测希尔排序的效率—随机给10000个数字,分别用希尔和直接插入排序来排序,观察所用的时间
void test2()
{
const int n = 100000;
int * a1 = (int *)malloc(sizeof(int)*n);
int * a2 = (int *)malloc(sizeof(int)*n);
srand(time(0));
for (int i = 0; i < n; i++)
{
a1[i] = rand();
a2[i] = a1[i];
}
size_t begin1 = clock();
InsertSort(a1, n);
size_t end1 = clock();
size_t begin2 = clock();
ShellSort(a2,n);
size_t end2 = clock();
printf("%u \n", end1 - begin1);
printf("%u\n", end2 - begin2);
}
看的出希尔排序的效率还是很高的
选择排序
每一趟(第i趟,i=0,1,…,n-2)在后面n-i个待排序的数据元素集合中选
出关键码最小的数据元素,作为有序元素序列的第i个元素。待到第n-2趟
做完,待排序元素集合中只剩下1个元素,排序结束。
- 直接选择排序--------没趟做了很多重复的比较,退排序解决了这个问题
思想:在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中
的最后一个(第一个)元素交换,在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,
重复上述步骤,直到集合剩余1个元素
每次 选出最小的元素,然后和第一个位置交换,缩小范围,在从剩下的数中选出次小的,和二个位置,用下标来操作
void SelectSort(int * arr, int size) //直接选择选择排序
{
for (int i = 0; i < size-1; i++) //剩下最后一个不用选
{
int MinPos = i;
for (int j = i; j < size; j++)
{
if (arr[MinPos]>arr[j])
{
MinPos = j;
}
}
Swap(&arr[MinPos],&arr[i] );
}
}
优化实现:每次从数据中选出最大的一个数和最小的一个数,最大的和最后位置交换,最小的和第一个位置换,缩小区间,重复工作
void SelectSort2(int * arr,int size) //优化----选择排序
{
int begin = 0;
int end = size - 1;
while (begin<end)
{
int min = begin;
int max = begin;
for (int i =begin; i <=end; i++) //在begin和end区间取选最大值,最小值
{
if (arr[min]> arr[i])
{
min = i;
}
if (arr[max] < arr[i])
{
max = i;
}
}
if (min != begin) //交换
{
Swap(&arr[min], &arr[begin]); //1 6 8 2 5 0
} //有个坑,当先进行大的交换的时候,min正好处于end位置,此时要进行更新min的位置,把它更新到max的位置
//或者max在 begin的 位置
if (begin == max) //相反如果先交换大的,就要考虑end上的位置是不是为min,就要更新min
{
max = min;
}
if (max != end)
{
Swap(&arr[max], &arr[end]);
}
begin++;
end--;
}
}
- 堆排序-----升序(大堆),降序(小堆),不稳定,时间复杂度是多少
交换排序
-
冒泡排序----------时间复杂度,是稳定的
思想:每一趟遍历把最大的(最小)的数据不断的交换到最后,就像冒泡一样浮了上来,然后在来一趟,需要n-1趟 -
快排
思想:取一个基准值,(升序),比基准值大的放它的右边,小的放它的左侧
hoare版本
挖坑法
前后指针法
递归排序的优化:1.三数取中选Key法。2.一开始可以考虑递归,一直递归到小的区间时可以考虑用直接插入排序
快排的非递归
快排的时间复杂度
//快排(重要)
//思想:单趟选出关键字(第一个,或者最后一个),放在合适的位置后,比他小的放左边,比它大的放右边
//如何放在合适的位置,三种方法(左右指针法,挖坑法,前后指针法)
//三数取中(为什么用它)
int GeTMiddle(int*arr, int left, int right)
{
int mid = left + (right - left) >>1;
if (arr[left] < arr[right])
{
if (arr[mid] < arr[left])
Swap(&arr[left], &arr[mid]);
else if (arr[mid]>arr[right])
Swap(&arr[right], &arr[mid]);
else
return mid;
}
return mid;
}
//左右指针法
int part1(int* arr,int left,int right) //单趟排序
{
int key = arr[right];
int begin = left;
int end = right;
while (begin < end)
{
//begin找大
while (begin < end && arr[begin] <= key) // =坑,没有=就停下来了,越界的坑
++begin;
//end找小的
while (begin < end && arr[end] >= key) //考虑一下不加=
--end;
if (begin < end)
Swap(&arr[begin], &arr[end]); //1 2 3 4
}
//begin 和end相遇的情况(begin先停下,end找begin,两者相遇的数比key大,把它放在后边,反过来两者相遇的数比key小,放在前边
//begin end相遇,停止
Swap(&arr[begin], &arr[right]);
return arr[begin];
}
//方法二,挖坑法
int Part2(int* arr,int left,int right)
{
int key = arr[right];
int begin = left;
int end = right;
while (begin < end)
{
while (begin < end && arr[begin] < key)
{
++begin;
}
arr[end] = arr[begin]; //补坑
while (begin < end && arr[end] >= key)
{
--end;
}
arr[begin] = arr[end];
}
//相遇的位置
arr[begin] = key;
return begin;
}
归并排序------等半划分
基本思想: 将待排序的元素序列分成两个长度相等的子序列,对每一个子序列排序然后将他们合并成一个序列。合并两个子序列的过程称为二路归并
重点是划分到最后一组的时候(即每组十五年感谢一个的时候怎么进行归并,其实就是两个数组借助第三个数组进行排序,比较,小的放进去,在比较,在放小的,所以归并是归并到一个新的数组中然后拷到原数组中,至于划分的时候其实是借助下标来控制区间的
第一:对区间划分成(递归划分)
第二:合并(两个有序数组合并成了一个数组)
void MergertData(int*arr, int left, int mid, int right, int *tmp)
{ //要划分成左右两个区间,所以得传左右区间数,中间数,
int beginL = left, endL = mid; //先划分两段 ,这是左数组
int beginR = mid+1, endR = right; //右数组
int index = left;
while (beginL < endL && beginR < endR)
{
if (arr[beginL] <= arr[beginR]) //左数组中的第一个数字和右边数组的第一个数字
tmp[index++] = arr[beginL];
else
tmp[index++] = arr[beginR];
}
//左边界里的没搬完
while (beginL < endL)
{
tmp[index++] = arr[beginL++];
}
while(beginR < endR)
{
tmp[index++] = array[beginR++];
}
}
void _MergertSort(int* arr, int left, int right, int* tmp) //划分方式
{
if (right - left > 1)
{
int mid = (left + (right - left) >> 2);
_MergertSort(arr, left, mid, tmp); //左边划分
_MergertSort(arr, mid + 1, right, tmp); //右边划分
MergertData(arr, left, mid, right, tmp);// 归并起来
}
}
void MergeSort(int* arr,int size) //归并排序
{
int* tmp = (int*)malloc(sizeof(int)*size);
if (tmp == NULL)
return;
_MergetSort(arr, 0, size);
free(tmp);
}
`