1、排序的概念:排序就是将一组数据按照一定的顺序,递增或递减排列起来。
2、排序的稳定性:对于两个关键字相等的记录,它们在序列中的相对位置,在排序之前和经过排序之后,没有改变。
3、内部排序与外部排序:内部排序指的是排序时需要将全部数据加载到内存中;外部排序是指所要排序的数据太大了不能同时在内存中进行。
4、八大排序:插入排序、希尔排序、选择排序、快速排序、冒泡排序、堆排序、归并排序、计数排序、基数排序
5、各种排序的实现及总结:
5.1、插入排序(直接插入排序与希尔排序):每步将一个待排序的纪录,按其关键码值的大小插入前面已经排序的元素序列中适当位置上,直到全部插入完为止。(类似于打扑克)
5.1.1、算法的实现及总结:
直接插入排序算法:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。
因此,从上面的描述中我们可以发现,直接插入排序可以用两个循环完成:
第一层循环:遍历待比较的所有数组元素
第二层循环:将本轮选择的元素(selected)与已经排好序的元素(ordered)相比较。
如果:selected > ordered,那么将二者交换
void Inser_sort(int* array, int size){
// 找待插入元素位置
for (int i = 0; i < size; i++){
int key = array[i];
int end = i - 1;
// 搬移元素
while (end >= 0 && key < array[end]){
array[end + 1] = array[end];
end--;
}
array[end + 1] = key;
}
}
// 直接插入排序的算法特点:
// 时间复杂度:O(N^2)
// 空间复杂度:O(1) 未借助辅助空间
// 稳定性:稳定 (根据是否间隔元素来判断)
// 应用场景:元素有限切接近有序,越有序排序效率越高
// 希尔排序(缩小增量排序)
// 先选定一个整数,把待排序文件中所有记录分成若干组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。
// 然后,取,重复上述分组和排序的工作。当到达 = 1时,所有记录在统一组内排好序。
void Shell_sort(int* array, int size){
int gap;
int i;
int key;
int end;
for (gap = size / 2; gap > 0; gap /= 2){
for (i = gap; i < size; i++){
end = i - gap; // 每次排序的最后一个元素位置
key = array[i]; // 每次排序的最后一个元素
while (end >= 0 && key < array[end]){
array[end + gap] = array[end];
end -= gap;
}
array[end + gap] = key;
}
}
}
// 希尔排序算法总结
// 时间复杂度:平均:O(N^1.3-N^2) 最坏:O(N^2)
// 空间复杂度:O(1)
// 稳定性: 不稳定
// 应用场景:当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就
// 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
5.2、选择排序(直接选择排序与堆排序):每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的 数据元素排完 。
5.2.1、算法的实现及总结:
// 选择排序
// 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
// 直接选择排序:
// 在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素
// 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
// 在剩余的array[i]--array[n - 2](array[i + 1]--array[n - 1])集合中,重复上述步骤,直到集合剩余1个元素
void Select_sort(int* array, int size){
for (int i = 0; iarray[child]){
return;
}
else{
swap(&array[parent], &array[child]);
parent = child;
child = parent * 2 + 1;
}
}
}
// 堆排序:以升序为例
void Heap_sort(int* array,int size){
// 建堆
for (int i = size / 2 - 1; i >= 0; i--){
HeapAdjust(array, size, i);
}
// 排序
for (int i = size - 1; i >= 0; i--){
swap(&array[0], &array[i]);
HeapAdjust(array, i, 0);
}
}
// 堆排序算法总结
// 时间复杂度:O(N*logN)
// 空间复杂度:O(1)
// 稳定性:不稳定
// 应用场景:数据量小时,效率较高
5.3、交换排序(冒泡排序与快速排序):所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排 序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
5.3.1、算法实现及总结:
// 冒泡排序:
// 相邻两个元素进行比较后交换
void Bubble_sort(int* array, int size){
for (int i = 0; i < size; i++){
int Mark = 0;
for (int j = 1; j < size; j++){
if (array[j - 1]>array[j]){
swap(&array[j - 1], &array[j]);
Mark = j + 1;
}
}
// 一趟比较完
if (Mark == 0){
break;
}
i = size - 1 - Mark;
}
}
// 冒泡排序算法总结:
// 时间复杂度:O(N^2)
// 空间复杂度:O(1)
// 稳定性:稳定
// 应用场景:有序数列冒泡排序最快
// 快速排序
// 区间分割法
// 取一个基准值
// 将剩余元素与基准值进行比较分组
int Partition(int* array, int left, int right){
int begin = left;
int end = right;
int key = array[right];
while (begin < end){
while (begin < end && array[begin] <= key){
begin++;
}
while (begin < end && array[end] >= key){
end--;
}
swap(&array[begin], &array[end]);
}
swap(&array[begin], &array[right-1]);
return begin;
}
void Quick_sort(int* array, int left, int right){
if (right - left > 1){
int div = Partition(array, left, right);
Quick_sort(array, left, div-1);
Quick_sort(array, div+1, right);
}
}
// 快速排序的非递归实现
// 需要先实现一个栈
void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, left);
StackPush(&st, right);
while (StackEmpty(&st) != 0)
{
int end = StackTop(&st);
StackPop(&st);
int begin = StackTop(&st);
StackPop(&st)
int div = PartSort1(a, begin, end);
if (begin < div - 1)
{
StackPush(&st, begin);
StackPush(&st, div - 1);
}
if (div + 1 < end)
{
StackPush(&st, div + 1);
StackPush(&st, end);
}
}
}
// 快速排序算法总结
// 时间复杂度:O(N*longN)
// 空间复杂度:O(longN)
// 稳定性:不稳定
// 应用场景:由于综合性能较好,所以在现实中使用较多
5.4、归并排序: 归并排序是将两个有序的数组归并成一个更大的有序数组。要将一个数组排序,可以先(递归的)将他分成两半分别排序,让后将结果归并起来。它能够保证将任意长度为N的数组排序所需时间和NlogN成正比;它的主要缺点就是所需的额外空间和N成正比。
5.4.1、算法的实现及总结:
int MergData(int* array, int left, int mid, int right, int* temp){
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int i = 0;
// 均分序列
while (begin1 <= end1 && begin2 <= end2){
if (array[begin1] < array[begin2]){
temp[i] = array[begin1];
begin1++;
}
else{
temp[i] = array[begin2];
begin2++;
}
}
while (begin1 <= end1){
temp[i++] = array[begin1];
begin1++;
}
while (begin2 <= end2){
temp[i++] = array[begin2];
begin2++;
}
}
void _Mergsort(int* array, int left,int right,int* temp){
if (right - left > 1){
// 均分序列
int mid = left + ((right - left) >> 1);
// 递归左右
_Mergsort(array, left, mid, temp);
_Mergsort(array, mid, right, temp);
// 归并数据到临时空间
MergData(array, left, mid, right, temp);
// 拷贝临时空间的元素到原空间中
memcpy(array + left, temp + left, sizeof(array)/sizeof(array[0]));
}
}
void Mergsort(int* array, int left ,int right){
int* temp = (int*)malloc(sizeof(int)*array[0]);
if (temp == NULL){
return;
}
_Mergsort(array, left,right,temp);
free(temp);
}
// 归并排序算法总结
// 时间复杂度:O(N*logN)
// 空间复杂度:O(N)
// 稳定性:稳定
// 应用场景:数据量较大且要求排序稳定时
// 优化措施:由于使用递归,递归深度太深容易造成内存溢出,所以可使用非递归版本归并排序
5.5、计数排序:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
操作步骤:
1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中
5.5.1、算法的实现及总结:
void countsort(int* array, int size){
int minval = array[0];
int maxval = array[0];
int i = 0;
int index = 0;
// 找区间
for (i = 0; i < size; i++){
if (array[i] < minval){
minval = array[i];
}
if (array[i] > maxval){
maxval = array[i];
}
}
int range = maxval - minval + 1;
// 给空间
int* temp = (int*)malloc(sizeof(int)*size);
if (temp == NULL){
assert(0);
return;
}
memset(temp, 0, sizeof(int)* range);
// 统计每个数据出现次数
for (i = 0; i < size; i++){
temp[array[i]] = minval++;
}
// 回收数据
for (i = 0; i < range; i++){
while (temp[i]--){
array[index++] = i + minval;
}
}
free(temp);
}
void print(int* array, int size){
for (int i = 0; i < size; i++){
printf("%d ");
}
printf("\n");
}
// 应用场景:计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
// 时间复杂度:O(MAX(N, 范围))
// 空间复杂度:O(范围)
// 稳定性:稳定
5.6、八大算法的比较: