###插入排序
void insert_sort(int *a, int size)
{
//插入排序:
//每一步将一个待排序的元素,插入到前面已经有序的一组元素中,
//直到元素排完为止。
assert(a);
assert(size>0);
for (int i = 1;i < size;i++)
{
int tmp = a[i];
int n = i;
while (n)
{
if (tmp < a[n - 1])
{
a[n] = a[n - 1];
n--;
}
else
break;
}
a[n] = tmp;
}
}
###希尔排序
void shell_sort(int *a, int size, int interval)
{
//希尔排序就是插入排序的优化版,分为2步:
//1.预排序:取一个值x作为间隔数,对应间隔为x的值比较大小,使数组基本有序
//2.插入排序(此时进行的插入排序的时间复杂度不再是O(n^2),因为已经基本有序)
assert(a);
assert(size > 0);
for (int i = 0;i < interval;i++)
{
if (a[i] > a[i + interval])
{
swap(&a[i], &a[i + interval]);
}
}
insert_sort(a, size);
}
###选择排序
void select_sort(int *a, int size)
{
//选择排序
//思想:每趟在后面的n-i(i为趟数)选一个最小的值,与第i个值交换,直至数组中只剩一个元素。
//时间复杂度:始终为O(n^2),所以不是一个很优的排序算法。
assert(a&&size > 0);
for (int i = 0;i<size - 1;i++)
{
int *min = &a[i];
int j = i;
while (j < size - 1)
{
if (*min > a[j + 1])
{
min = &a[j + 1];
}
j++;
}
if (min != &a[i])
{
swap(min, &a[i]);
}
}
}
###堆排序
void heapadjustdown(int *a, int size, int parent)
{
//向下调整算法
assert(a&&size > 0);
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size&&a[child + 1] > a[child])
{
child++;
}
if (a[parent] < a[child])
{
swap(&a[parent], &a[child]);
}
parent = child;
child = parent * 2 + 1;
}
}
void heap_sort(int *a, int size)
{
//堆排序
//思想:升序:建大堆;降序:建小堆
//建好堆后,将堆顶元素与最后一个元素进行交换,堆的大小-1,对堆顶元素进行向下调整,直至堆内只有一个元素
assert(a&&size > 0);
int parent = ((size - 1) - 1) / 2;
int child = parent * 2 + 1;
int len = size;
while (parent >= 0)
{
heapadjustdown(a, size, parent);
parent--;
}
//建好堆了
while (len > 1)
{
swap(&a[0], &a[len - 1]);
len--;
heapadjustdown(a, len, 0);
}
}
###冒泡排序
void bubble_sort(int *a, int size)
{
//冒泡排序
assert(a&&size > 0);
for (int i = 0;i < size;i++)
{
int flag = 1;
for (int j = 0;j < size - i - 1;j++)
{
if (a[j] > a[j + 1])
{
swap(&a[j], &a[j + 1]);
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
###快速排序其一(左右指针法,hoare法)
int GetMidIndex(int *a, int left, int right)
{
int mid = left + (right - left) / 2;//取到中间位置的下标
if (a[left] < a[mid])
{
if (a[right] < a[left])
{
return left;
}
if (a[right] > a[mid])
{
return mid;
}
else
{
return right;
}
}
else
{
if (a[right] > a[left])
{
return left;
}
if (a[right] < a[mid])
{
return mid;
}
else
return right;
}
}
int quick_sort1(int *a, int left, int right)
//左右指针法(hoare版本)
//单趟快排达到的效果是该数放在它排好的位置上(左边的数都比它小,右边的数都比它大)
//再对其进行左右子排序,直至整个序列都有序
{
assert(a);
int begin = left;
int end = right;
int key = GetMidIndex(a, left, right);//三数取中法,能减少排序次数。
swap(&a[key], &a[right]);//方便起见,把中位数放到最右边。
while (begin < end)
{
while (begin < end&&a[begin] <= a[right])
{
begin++;
}
while (begin < end&&a[end] >= a[right])
{
end--;
}
if (begin < end)
{
swap(&a[begin], &a[end]);
}
}
swap(&a[begin], &a[right]);//将中位数放到正确的位置,至此该数不用在做变化
print_arr(a, 10);
printf("\n");
return begin;//返回最终放置中位数的下标,为后序递归确定边界
}
void quick_sort(int *a, int left, int right)
{
assert(a);
if (left >= right)
{
return;
}
int mid = quick_sort1(a, left, right);
quick_sort(a, left, mid - 1);
quick_sort(a, mid + 1, right);
}
###快速排序其二(挖坑法)
开始时,7为坑值key,begin向右找,找到9比7大,用9填7处的坑,自己成为新的坑,end向左找,找到1比7小,则用1填9的坑,自己成为新坑,如此重复直至begin==end,将key值7填入begin出,算作一次排序。
int quicksort2(int *a, int left, int right)
{
assert(a);
int begin = left;
int end = right;
int key = a[right];
while (begin < end)
{
while (begin<end && a[begin] <= key)
{
begin++;
}
a[end] = a[begin];
while (begin<end && a[end] >= key)
{
end--;
}
a[begin] = a[end];
}
a[begin] = key;
return begin;
}
void quick_sort(int *a, int left, int right)
{
assert(a);
if (left >= right)
{
return;
}
int mid = quicksort2(a, left, right);
quick_sort(a, left, mid - 1);
quick_sort(a, mid + 1, right);
}
###快速排序其三(前后指针法)
int quicksort3(int *a, int left, int right)
{
//定义两个指针,prev和cur,再定义key的值,cur指针从left开始,
//遇到比key大的就过滤掉,比key小的,就停下来,prev++,
//判断prev和cur是否相等,如果不相等,就将两个值进行交换
assert(a);
int cur = left;
int prev=left-1;
int key = GetMidIndex(a, left, right);
swap(&a[key], &a[right]);
while (cur != right)
{
while (a[cur] < a[right] && (++prev)!=cur)
{
swap(&a[prev], &a[cur]);
}
cur++;
}
swap(&a[++prev], &a[right]);
return prev;
}
void quick_sort(int *a, int left, int right)
{
assert(a);
if (left >= right)
{
return;
}
int mid = quicksort3(a, left, right);
quick_sort(a, left, mid - 1);
quick_sort(a, mid + 1, right);
}
###快速排序之非递归
void QuickSortNonR(int *a, int left, int right)//快排非递归
{
assert(a);
if (left >= right)
return;
Stack st;
StackInit(&st);
int topl;//栈顶的左
int topr;//栈顶的右
int div = 0;
//进栈顺序:先左后右
StackPush(&st, left);
StackPush(&st, right);
while (StackEmpty(&st))
{
//出栈先右后左
topr = StackTop(&st);
StackPop(&st);
topl = StackTop(&st);
StackPop(&st);
div = partsort1(a, topl, topr);
// topr div-1 div div+1 topr
//div的左边区间先入栈,右边区间后入栈,但是栈先进后出,先排右边区间再排左边区间
if (topl < div - 1)
{
StackPush(&st, topl);
StackPush(&st, div - 1);
}
if (div + 1 < topr)
{
StackPush(&st, div + 1);
StackPush(&st, topr);
}
}
}
快速排序在数据较多时具有很大优势,但数据少时却没有之前的几种方法便捷,比如,要排5个数,用左右指针法,要递归调用7次quicksort,此时就不如插入排序来的方便了,所以为提高效率,我们可以做一下优化:当数据量小于某数值时(比如20),就不用快速排序,转而使用插入排序
if(right-left+1<20)
{
insert_sort(a+left , right-left+1);
}
else
{
.......
}
###归并排序
归并排序又称外排序,可以在磁盘中进行排序,不用读入内存
void _MergeSort(int *a, int left, int right, int *tmp)
{
if (left >= right)
return;
if (right - left + 1 < 20)
{
InsertSort(a + left, right - left + 1);//小区间优化:当数据个数(闭区间需要加1)小于20时,直接插入排序
return;
}
int mid = left + (right - left) / 2;
_MergeSort(a, left, mid, tmp);//将左边划分为有序
_MergeSort(a, mid + 1, right, tmp);//将右边划分语序
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int index = left;
// begin1--end1是有序区间 begin2--end2是有序区间
//将两段有序区间合并为一段有序区间
while (begin1 <= end1 && begin2 <= end2)
{
//把小的数据放在tmp中
if (a[begin1] <= a[begin2])//等于号保证归并是稳定的
{
tmp[index] = a[begin1];
index++;
begin1++;
}
else
{
tmp[index] = a[begin2];
index++;
begin2++;
}
}
if (begin1 > end1)//说明begin2-end2还有数据
{
while (begin2 <= end2)
tmp[index++] = a[begin2++];
}
else //说明begin1 - end1还有数据
{
while (begin1 <= end1)
tmp[index++] = a[begin1++];
}
index = left;
while (index <= right)//由于tmp只是个临时数组,需要将有序数据重新放到数组a中
{
a[index] = tmp[index];
index++;
}
}
void MergeSort(int *a, int n)
{
assert(a);
int *tmp = (int *)malloc(sizeof(int)*n);
_MergeSort(a, 0, n - 1, tmp);//tmp是临时数组
free(tmp);
tmp = NULL;
}
排序性能比较:
1.选择排序和插入排序:插入排序较好
两者时间复杂度都为0(n^2),但对于有序的插入排序时间复杂度为0(n),
直接插入在有序区间后面,不用再挪动数据,选择排序依然是0(n^2);
2.冒泡排序、插入排序、选择排序:插入排序较好
如 1 2 3 5 4
插入排序是5次可以有序,而冒泡在4和5交换有序后还需要再判断是否有序,才结束排序,即7次
3.经过测试,大量数据快排性能优于堆排
各种排序稳定性:
稳定性指两个相同的值在排序后相对位置不变。
插入排序:稳定(相等就插在后面)
希尔排序:不稳定(有gap,跳跃着排序,可能回影响相对位置)
选择排序:不稳定(3 5 9* 9 2 7—>2 5 7 9 3 9*)
冒泡排序:稳定(两个数据相等,不交换)
堆排:不稳定(5 4 5*,第一个5和最后一个5交换,相对位置发生变化)
快速排序:不稳定( 6 7 1 5 5—>1 5 6 5* 7)
归并排序:稳定(相等就将前一个数据放入tmp数组)