1.排序的概念及其运用
1.1排序的概念
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的分列起来的操作。
稳固性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r=r[j],且r在r[j]之前,而在排序后的序列中,r仍在r[j]之前,则称这种排 序算法是稳固的;否则称为不稳固的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在表里存之间移动数据的排序。
1.2排序运用
1.3 常见的排序算法
2、常见排序算法思想
2.1 插入排序
2.11 根本思想:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到全部的 记录插入完为止,得到一个新的有序序列 。当我们排序扑克牌是就是用的这种思想。
2.12 直接插入排序
当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array的排序码与 array[i-1],array[i-2],…的排序码顺序进行比力,找到插入位置即将array插入,原来位置上的元素顺序后移。
代码演示:
- void InsertSort(int* a, int n)
- {
- for (int i = 0;i < n - 1;i++)
- {
- int tmp = a[i + 1];
- int end = i;
- while (end >= 0)
- {
- if (a[end] > tmp)
- {
- a[end + 1] = a[end];
- end--;
- }
- else
- {
- break;
- }
- }
- a[end + 1] = tmp;
- }
- }
复制代码 注:插入排序一般都是在原数组上进行操作,不用再创建一个数组 。可以结合代码和上图一同看。
直接插入排序的特性总结:
1. 元素集合越接近有序,直接插入排序算法的时间服从越高
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1),它是一种稳固的排序算法
4. 稳固性:稳固
2.1.3 希尔排序( 缩小增量排序 )
希尔排序法又称缩小增量法。希尔排序法的根本思想是:先选定一个整数,把待排序文件中全部记录分成个 组,全部距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工 作。当到达=1时,全部记录在统一组内排好序。
简单来说希尔排序就是把一个数组变得比力有序,然后再进行插入排序。因为通过简单的盘算我们能发现,插入排序在有序的情况下时间复杂度是O(N)。
代码演示:
- void ShellSort(int* a, int n)
- {
- int gap = n;
- while (gap > 1)
- {
- gap /= 2;
- for (int i = 0;i < n - gap;i++)
- {
- int tmp = a[i + gap];
- int end = i;
- while (end >= 0)
- {
- if (a[end] > tmp)
- {
- a[end + gap] = a[end];
- end = end - gap;
- }
- else
- {
- break;
- }
- }
- a[end + gap] = tmp;
- }
- }
- }
复制代码 注:如果是一趟一趟看的话建议可以多加一层循环,时间复杂度还是不变的。这里gap就相称于前面插入排序的1,只不外这里跳的步数大。
1. 希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序,目标是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样团体而言,可以到达优化的结果。我们实现后可以进行性能测试的对比。
3. 希尔排序的时间复杂度不好盘算,因为gap的取值方法很多,导致很难去盘算,因此在好些树中给出的 希尔排序的时间复杂度都不固定。
4. 稳固性:不稳固
2.2 选择排序
2.2.1根本思想:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的 数据元素排完 。
2.2.2 直接选择排序:
在元素集合array--array[n-1]中选择关键码最大(小)的数据元素 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素互换 在剩余的array--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素
代码演示:
- void swap(int* p1, int* p2)
- {
- int tmp = *p1;
- *p1 = *p2;
- *p2 = tmp;
- }
- void SelectSort(int* a, int n)
- {
- int left = 0;
- int right = n - 1;
- int max = 0;
- int min = 0;
- while (left < right)
- {
- max = left;
- min = right;
- for (int i = left;i <= right;i++)
- {
- if (a[i] > a[max])
- {
- max = i;
- }
- if (a[i] < a[min])
- {
- min = i;
- }
- }
- swap(&a[left], &a[min]);
- if (left == max)
- {
- max = min;
- }
- swap(&a[right], &a[max]);
- left++;
- right--;
- }
- }
复制代码 注:代码中的swap函数为互换函数 ,当左指针指向的值为max时,我们需要加一个把max的值重新换回来的判断,否则会将最大值与最小值互换,那么最大值的指针指向的就不是最大值了。反之同理。
直接选择排序的特性总结:
1. 直接选择排序思考非常好理解,但是服从不是很好。实际中很少使用
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳固性:不稳固
2.2.3 堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种数据布局所设计的一种排序算法,它是选择排序的一种。它是 通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
代码演示:
- void swap(int* p1, int* p2)
- {
- int tmp = *p1;
- *p1 = *p2;
- *p2 = tmp;
- }
- void adjustdwon(int* a, int n, int parent)
- {
- int child = (parent * 2) + 1;
- while (child < n)
- {
- if (child + 1 < n && a[child] < a[child + 1])
- {
- child++;
- }
- if (a[child] > a[parent])
- {
- swap(&a[child], &a[parent]);
- parent = child;
- child = (parent * 2) + 1;
- }
- else
- {
- break;
- }
- }
- }
- void HeapSort(int* a, int n)
- {
- int i = 0;
- for (i = (n - 1 - 1) / 2;i >= 0;i--)
- {
- adjustdwon(a, n, i);
- }
- int end = n - 1;
- while (end > 0)
- {
- swap(&a[end], &a[0]);
- adjustdwon(a, end, 0);
- end--;
- }
- }
复制代码 注: 因为向下调整建大堆时,要左右子树都为大堆因此我们要从最后一个不是叶子节点的 位置开始向下调整建大堆,而这个位置就是最后一个叶子节点的父亲。每次拿到最大值以后与最后位置互换,之后就不要动最大值了。
直接选择排序的特性总结:
1. 堆排序使用堆来选数,服从就高了很多。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(1)
4. 稳固性:不稳固
2.3 互换排序
根本思想:所谓互换,就是根据序列中两个记录键值的比力结果来对换这两个记录在序列中的位置,互换排 序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
2.3.1冒泡排序
根本思想:两个数字相互比力大的换到小的前面,云云反复。当一趟走完以后,最大的会在最后面。
代码演示:
- void swap(int* p1, int* p2)
- {
- int tmp = *p1;
- *p1 = *p2;
- *p2 = tmp;
- }
- void BubbleSort(int* a, int n)
- {
- for (int j = 0;j < n;j++)
- {
- for (int i = 1;i < n - j;i++)
- {
- if (a[i - 1] > a[i])
- {
- swap(&a[i - 1], &a[i]);
- }
- }
- }
- }
复制代码 冒泡排序的特性总结:
1. 冒泡排序是一种非常容易理解的排序
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳固性:稳固
2.3.2 快速排序
快速排序是Hoare于1962年提出的一种二叉树布局的互换排序方法,其根本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中全部元素均小于基准值,右 子序列中全部元素均大于基准值,然后最左右子序列重复该过程,直到全部元素都分列在相应位置上为止。
1. hoare版本
可以先看一个动图:

代码演示:
- void swap(int* p1, int* p2)
- {
- int tmp = *p1;
- *p1 = *p2;
- *p2 = tmp;
- }
- int getmidi(int* a, int left, int right)
- {
- int midi = (left + right) / 2;
- if (a[left] < a[right])
- {
- if (a[midi] > a[left])
- {
- return midi;
- }
- else if (a[midi] < a[left])
- {
- return left;
- }
- else
- {
- return right;
- }
- }
- //(a[left]>a[right])
- else
- {
- if (a[midi] >a[right])
- {
- return midi;
- }
- else if (a[midi] < a[right])
- {
- return right;
- }
- else
- {
- return left;
- }
- }
- }
- int PartSort1(int* a, int left, int right)
- {
- //随机取值
- /*int randi = left + (rand() % (right - left));
- swap(&a[left], &a[randi]);
- int keyi = left;*/
- //三数取中
- int midi = getmidi(a, left, right);
- swap(&a[left], &a[midi]);
- int keyi = left;
- while (left < right)
- {
- while (left < right && a[right]>=a[keyi])
- {
- right--;
- }
- while (left < right && a[left] <= a[keyi])
- {
- left++;
- }
- swap(&a[left], &a[right]);
- }
- swap(&a[left], &a[keyi]);
- keyi = left;
- return keyi;
- }
- void QuickSort(int* a, int left, int right)
- {
- if (left >= right)
- {
- return;
- }
- int keyi = PartSort1(a, left, right);
- QuickSort(a, left, keyi - 1);
- QuickSort(a, keyi + 1, right);
- }
复制代码 注:代码里面的随机取值是为了让key值取一个比力随机的值,因为当数组为有序时,如果用常规的Hoare快排会让时间复杂度变成(N^2)下面将用一个图表明一下。

这里一般是有两种方法第一个就是取随机值像上面代码呈现的一样,第二个就是三数取中在左边中间右边位置,取一个中间值。如果碰到大量重复数据,快排的服从会大大低沉,这里就需要三路分别的方法。( keyi代表key值的下标)
2. 挖坑法
挖坑其实和 hoare版本十分相似,唯一差别的就是将key值拿到外面去制造一个坑位(这里注意还是要保持左边做key右边先走的思想,这里是左边的值拿出来右边就先走)然后右边找到一个小于key值的数放到坑里。左边再找小于key的值放到右边坑位,云云反复。
代码演示:
- void swap(int* p1, int* p2)
- {
- int tmp = *p1;
- *p1 = *p2;
- *p2 = tmp;
- }
- int getmidi(int* a, int left, int right)
- {
- int midi = (left + right) / 2;
- if (a[left] < a[right])
- {
- if (a[midi] > a[left])
- {
- return midi;
- }
- else if (a[midi] < a[left])
- {
- return left;
- }
- else
- {
- return right;
- }
- }
- //(a[left]>a[right])
- else
- {
- if (a[midi] >a[right])
- {
- return midi;
- }
- else if (a[midi] < a[right])
- {
- return right;
- }
- else
- {
- return left;
- }
- }
- }
- int PartSort2(int* a, int left, int right)
- {
- int midi = getmidi(a, left, right);
- if (midi != left)
- swap(&a[left], &a[midi]);
- int key = a[left];
- int hole = left;
- while (left < right)
- {
- while (left < right && a[right] >= key)
- right--;
- a[hole] = a[right];
- hole = right;
- while (left < right && a[left] <= key)
- left++;
- a[hole] = a[left];
- hole = left;
- swap(&a[left], &a[hole]);
- }
- a[hole] = key;
- return hole;
- }
- void QuickSort(int* a, int left, int right)
- {
- if (left >= right)
- {
- return;
- }
- int keyi = PartSort2(a, left, right);
- QuickSort(a, left, keyi - 1);
- QuickSort(a, keyi + 1, right);
- }
复制代码 3. 前后指针版本
- void swap(int* p1, int* p2)
- {
- int tmp = *p1;
- *p1 = *p2;
- *p2 = tmp;
- }
- int getmidi(int* a, int left, int right)
- {
- int midi = (left + right) / 2;
- if (a[left] < a[right])
- {
- if (a[midi] > a[left])
- {
- return midi;
- }
- else if (a[midi] < a[left])
- {
- return left;
- }
- else
- {
- return right;
- }
- }
- //(a[left]>a[right])
- else
- {
- if (a[midi] >a[right])
- {
- return midi;
- }
- else if (a[midi] < a[right])
- {
- return right;
- }
- else
- {
- return left;
- }
- }
- }
- int PartSort3(int* a, int left, int right)
- {
- int midi = getmidi(a, left, right);
- if (midi != left)
- swap(&a[left], &a[midi]);
- int prev = left;
- int cur = left + 1;
- int keyi = left;
- while (cur <= right)
- {
- if (a[cur] < a[keyi])
- {
- prev++;
- swap(&a[cur], &a[prev]);
- cur++;
- }
- else
- {
- cur++;
- }
- }
- swap(&a[keyi], &a[prev]);
- keyi = prev;
- return keyi;
- }
- void QuickSort(int* a, int left, int right)
- {
- if (left >= right)
- {
- return;
- }
- int keyi = PartSort3(a, left, right);
- QuickSort(a, left, keyi - 1);
- QuickSort(a, keyi + 1, right);
- }
复制代码 2.3.3 快速排序非递归
因为快排与二叉树的前序遍历十分相像,因此快排的非递归我们能用栈来实现。
具体步骤如下图:
- void swap(int* p1, int* p2)
- {
- int tmp = *p1;
- *p1 = *p2;
- *p2 = tmp;
- }
- int getmidi(int* a, int left, int right)
- {
- int midi = (left + right) / 2;
- if (a[left] < a[right])
- {
- if (a[midi] > a[left])
- {
- return midi;
- }
- else if (a[midi] < a[left])
- {
- return left;
- }
- else
- {
- return right;
- }
- }
- //(a[left]>a[right])
- else
- {
- if (a[midi] >a[right])
- {
- return midi;
- }
- else if (a[midi] < a[right])
- {
- return right;
- }
- else
- {
- return left;
- }
- }
- }
- void QuickSortNonR(int* a, int left, int right)
- {
- st st;
- stinit(&st);
- stpush(&st, right);
- stpush(&st, left);
- while (!stempty(&st))
- {
- int begin = sttop(&st);
- stpop(&st);
- int end = sttop(&st);
- stpop(&st);
- int keyi= PartSort3(a, begin,end);
- if (keyi + 1 < end)
- {
- stpush(&st, end);
- stpush(&st, keyi + 1);
- }
- if ( keyi - 1>begin)
- {
- stpush(&st, keyi-1);
- stpush(&st, left);
- }
- }
- }
复制代码
快速排序的特性总结:
1. 快速排序团体的综合性能和使用场景都是比力好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(logN)
4. 稳固性:不稳固
2.4 归并排序
根本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是接纳分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并排序焦点步骤:
- void _MergeSort(int*a, int* tmp, int begin,int end )
- {
- if (begin >= end)
- {
- return;
- }
- int mid = (end + begin) / 2;
- _MergeSort(a, tmp, begin,mid);
- _MergeSort(a, tmp, mid+1, end);
- int begin1 = begin, end1 = mid;
- int begin2 = mid + 1, end2 = end;
- int j = begin;
- 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 + begin, tmp + begin, sizeof(int)*(end - begin + 1));
- }
- void MergeSort(int* a, int n)
- {
- int* tmp = (int*)malloc(sizeof(int) * n);
- if (tmp == NULL)
- {
- perror("malloc fail");
- return;
- }
- _MergeSort(a, tmp, 0, n - 1);
- free(tmp);
- }
复制代码 2.4.1 归并排序的非递归
- void MergeSortNonR(int* a, int n)
- {
- int* tmp = (int*)malloc(sizeof(int) * n);
- if (tmp == NULL)
- {
- perror("malloc fail");
- return;
- }
- int gap = 1;
- while (gap < n)
- {
- for (int i = 0;i < n;i += 2 * gap)
- {
- int begin1 = i, end1 = i + gap - 1;
- int begin2 = i + gap, end2 = i + 2 * gap - 1;
- if (end1 >= n || begin2 >= n)
- {
- break;
- }
- if (end2 >= n)
- {
- end2 = n - 1;
- }
- int j = i;
- 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 + i, tmp + i, sizeof(int) * (end2 - i + 1));
- }
- gap = 2 * gap;
- }
- }
复制代码 注:begin1 end1 begin2 end 2那部分代码不理解的可以本身套数
归并排序的特性总结:
1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是办理在磁盘中的外排序问题。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(N)
4. 稳固性:稳固
2.5 非比力排序
思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
操作步骤: 1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中
- void CountSort(int* a, int n)
- {
- int max = a[0];
- int min = a[0];
-
- for (int i = 1;i < n;i++)
- {
- if (max < a[i])
- {
- max = a[i];
- }
- if (min > a[i])
- {
- min = a[i];
- }
- }
- int range = max - min+1;
- int* counta = (int*)malloc(sizeof(int) * range);
- if (counta == NULL)
- {
- perror("malloc fail");
- return;
- }
- memset(counta, 0, sizeof(int) * range);
- //计数
- for (int i = 0;i < n;i++)
- {
- counta[a[i] - min]++;
- }
- //排序
- int j = 0;
- for (int i = 0;i< range;i++)
- {
- while (counta[i]--)
- {
- a[j++] = i+ min;
- }
- }
- free(counta);
- }
复制代码 注:这里计数数组映射的相对值,因此最后取出来要加最小值。
计数排序的特性总结:
1. 计数排序在数据范围集中时,服从很高,但是实用范围及场景有限。
2. 时间复杂度:O(MAX(N,范围))
3. 空间复杂度:O(范围) 比特科技
4. 稳固性:稳固
3.排序算法复杂度及稳固性分析
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |