排序
简单排序(差劲排序)
下面的几种排序都是非常简单容易实现的,不过也有着相应的问题,他们在正常的情况下排序,时间复杂度几乎都是\(O(N^2)\)。
冒泡排序
冒泡冒泡,大的沉底,小的浮起。
基本思路是循环n-1次,每次把最大的元素沉底。
从j遍历到i,如果j比他的下一个元素(j+1)大,就交换他们。还有一个小技巧判断整个序列是否已经有序,无需继续执行。首先每次循环默认有序,如果便利完了都没有发生交换,说明已经有序退出即可。
算法最优时间复杂度\(O(N)\),整个序列完全有序。
算法最坏时间复杂度\(O(N^2)\),整个序列完全逆序。
//冒泡排序
void Bubble_Sort(int array[],int n) {
for (int i = n-1; i >= 0; i--)
{
bool isend = true;
for (int j = 0; j < i; j++)
{
//swap j and j+1
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j+1];
array[j + 1] = temp;
isend = false;
}
}
if (isend) {
break;
}
}
}
插入排序
基本的原理类似于我们玩扑克时的插排。
每次从未排序的数组中选一个,插到前面已经有序的序列里。初始从array[1]开始,向前扫描,如果array[0]如果比array[1]大,把0移到1的位置上,循环n-1次。
算法最优时间复杂度\(O(N)\),整个序列完全有序。
算法最坏时间复杂度\(O(N^2)\),整个序列完全逆序。
//插入排序
void Insertion_Sort(int array[], int n) {
for (int i = 1; i < n; i++)
{
int temp = array[i];
int j = 0;
for (j = i; j > 0&&array[j-1]>temp; j--)
{
array[j] = array[j - 1];
}
array[j] = temp;
}
}
选择排序
非常沙雕的一个排序,虽然很容易理解,但实在是太糟糕了。
基本思路是循环i:0到n-1,每次从数组中找到最小的放在i的的位置。
算法时间复杂度始终为\(O(N^2)\)。
//选择排序
void Selection_Sort(int array[], int n) {
for (int i = 0; i < n-1; i++)
{
int point = i;
for (int j = i + 1; j < n; j++) {
if (array[j] < array[point]) {
point = j;
}
}
//swap
if (point != i) {
int temp = array[i];
array[i] = array[point];
array[point] = temp;
}
}
}
高级排序
下面等级重排序相比上面略微难理解一些,不过相应的,他们的时间复杂度都有了质的变化。
希尔排序
作为插入排序的进阶版,速度提有了很大的提升,但面对特定的序列时可能会与选择排序达到同样的时间复杂。
基本思路是做一个增量序列{\(k_1,k_2,k_3,k_4,k_5...k_n\)}。
循环的i:1到k,每次以\(k_i\)为间隔做选择排序。在初始的版本中,选择的增量序列为数组元素个数的一半,每次循环变为原来的一半,直到为1时结束。不过这样的方法因为选定的增量序列不互质,给定特殊的序列例如{\(1,9,2,10,3,11,4,12\)}时,我们发现直到增量变为1时,压根就没有起效果,比插入排序还慢。
选择合理的增量序列有时候会有奇效,选择不好的增量序列时也会非常头疼。
传统的希尔排序算法最坏时间复杂度\(O(N^2)\)。
wiki中有更详细的其他的增量序列
//希尔排序
void Shell_Sort(int array[], int n) {
//i为增量序列,每隔i个的元素做插入排序
for (int i = n/2; i > 0; i /=2)
{
for (int j = i; j < n; j++)
{
int temp = array[j];
int k = 0;
for (k = j; k>=i && array[k-i]>temp; k-=i)
{
array[k] = array[k - i];
}
array[k] = temp;
}
}
}
堆排序
基本思路是先将传入的数组调成一个最大堆,每次将堆顶元素拿出,与堆的末尾元素交换,再将堆的容量减1 ,再接着调整堆,直到堆变空。
//调整堆
void Adjustment(int array[],int n,int k) {
//下标K代表数组元素array[k]空缺
int temp = array[k];
for (int j = k * 2 + 1; j < n; j = 2 * j + 1)
{
if (j + 1 < n && array[j + 1] > array[j]) {
j++;
}
if (array[j] > temp) {
array[k] = array[j];
k = j;
}
else {
break;
}
}
array[k] = temp;
}
//堆排序
void Heap_Sort(int array[], int n) {
//使数组变成最大堆
for (int i = n/2-1; i >= 0; i--)
{
Adjustment(array, n, i);
}
//每次交换最大元素array[0]和堆的最后一位,每次堆大小减1
for (int i = n-1; i > 0; i--)
{
int temp = array[i];
array[i] = array[0];
array[0] = temp;
Adjustment(array, i, 0);
}
}
归并排序
归并排序是一种典型的分而治之思想,基本思路是递归的将一个数组一分为2,将左右排好序后,合并他们。对于每个一分为2的数组又可以接着递归,直到要分的数组大小已经为1便不再去分。
我们都知到调用地规并不是一个好方法,可能会系统爆栈,这里通过循环实现。
思路是,一个大小为N的数组,把他想成N个大小为1的有序数组,每次循环两个两个合并起来成为大小为2的有序数组 ,再接着合并大小为4的有序数组,直到合并为一整个数组,也实现了整个数组的排序。该方法坑略多,具体操作在代码中加了注释。
//合并2个有序子序列
void Merge(int array[],int temparray[],int head1,int head2,int rightend) {
int leftend = head2 - 1;
int i = head1, j = head2, k = head1;
//循环条件为要合并的两个小数组都不空
while (i<=leftend&&j<=rightend)
{
if (array[i] <= array[j]) {
temparray[k++] = array[i];
i++;
}
else {
temparray[k++] = array[j];
j++;
}
}
//将剩余元素填入
while (i <= leftend)
{
temparray[k++] = array[i++];
}
while (j <= rightend)
{
temparray[k++] = array[j++];
}
//将临时数组中的元素移回原数组
for (i = head1; i <= rightend; i++)
{
array[i] = temparray[i];
}
}
//按长度length归并
void Merge_Pass(int array[], int temparray[],int n,int length) {
int i;
//完全的两两合并
for (i = 0; i <= n-2*length; i += 2 * length)
{
Merge(array, temparray, i, i + length, i + 2 * length - 1);
}
//残缺的两两合并
if (i + length < n) {
Merge(array, temparray, i, i + length, n-1);
}
//残的一个都没有,直接倒回去
else {
for (int j = 0; j < n; j++)
{
temparray[j] = array[j];
}
}
}
//归并排序
void Merge_Sort(int array[], int n) {
int length = 1;
int* temparray = (int*)malloc(n * sizeof(array));
if (temparray != NULL) {
while (length<n)
{
Merge_Pass(array, temparray, n, length);
length *= 2;
Merge_Pass(temparray, array, n, length);
length *= 2;
}
free(temparray);
}
}
三个小题
09-排序1 排序 (25point(s))
给定N个(长整型范围内的)整数,要求输出从小到大排序后的结果。
本题旨在测试各种不同的排序算法在各种数据情况下的表现。各组测试数据特点如下:
数据1:只有1个元素;
数据2:11个不相同的整数,测试基本正确性;
数据3:103个随机整数;
数据4:104个随机整数;
数据5:105个随机整数;
数据6:105个顺序整数;
数据7:105个逆序整数;
数据8:105个基本有序的整数;
数据9:105个随机正整数,每个数字不超过1000。
输入格式:
输入第一行给出正整数N(≤10
5
),随后一行给出N个(长整型范围内的)整数,其间以空格分隔。
输出格式:
在一行中输出从小到大排序后的结果,数字间以1个空格分隔,行末不得有多余空格。
输入样例:
输出样例:
题解:
没什么好说的,自行尝试上面的各种排序(部分排序因为过于慢,会导致长时间判断中)
#include<algorithm>
#include<cstdbool>
#include<cstdio>
using namespace std;
int arr[100000];
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
sort(arr, arr + n);
bool isf = true;
for (int i = 0; i < n; i++)
{
if (isf) {
printf("%d", arr[i]);
isf = false;
}
else {
printf(" %d", arr[i]);
}
}
}
09-排序2 Insert or Merge (25point(s))
According to Wikipedia:
Insertion sort iterates, consuming one input element each repetition, and growing a sorted output list. Each iteration, insertion sort removes one element from the input data, finds the location it belongs within the sorted list, and inserts it there. It repeats until no input elements remain.
Merge sort works as follows: Divide the unsorted list into N sublists, each containing 1 element (a list of 1 element is considered sorted). Then repeatedly merge two adjacent sublists to produce new sorted sublists until there is only 1 sublist remaining.
Now given the initial sequence of integers, together with a sequence which is a result of several iterations of some sorting method, can you tell which sorting method we are using?
Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤100). Then in the next line, N integers are given as the initial sequence. The last line contains the partially sorted sequence of the N numbers. It is assumed that the target sequence is always ascending. All the numbers in a line are separated by a space.
Output Specification:
For each test case, print in the first line either "Insertion Sort" or "Merge Sort" to indicate the method used to obtain the partial result. Then run this method for one more iteration and output in the second line the resuling sequence. It is guaranteed that the answer is unique for each test case. All the numbers in a line must be separated by a space, and there must be no extra space at the end of the line.
Sample Input 1:
Sample Output 1:
Sample Input 2:
Sample Output 2:
题解:
根据插入排序的性质,可以很容易判断出插入排序。
首先从0开始扫描,直到扫描到逆序的点,接着从逆序点p开始向后扫描到n-1,如果array1和array2都一样,说明一定是插入排序,否则为归并排序。
对插入排序,再对逆序的位置插一次。
对归并排序,我们先分析一下要能分清楚插入还是归并,肯定至少经过一步,但是也不能完全排序,所以归并至少每2个元素组成的数组有序,那么,如何更高的判断4个的情况呢,就是取中间值,如果长度为n的小数组中点小于他的前节点,就说明是无序的,且排到有序长度正好为该长度的一半。
所以从2开始,每次加2倍的2,判断是否达到了4。{2,6,10,14......}。
如果4成功通过,接着{4,12,20......},依次类推。
#include<algorithm>
#include<cstdbool>
#include<cstdio>
#define MAXSIZE 101
using namespace std;
//调整堆
void Adjustment(int array[], int n, int k) {
//下标K代表数组元素array[k]空缺
int temp = array[k];
for (int j = k * 2 + 1; j < n; j = 2 * j + 1)
{
if (j + 1 < n && array[j + 1] > array[j]) {
j++;
}
if (array[j] > temp) {
array[k] = array[j];
k = j;
}
else {
break;
}
}
array[k] = temp;
}
//打印数组
void PrintArray(int arr[], int n) {
bool isf = true;
for (int i = 0; i < n; i++)
{
if (isf) {
printf("%d", arr[i]);
isf = false;
}
else {
printf(" %d", arr[i]);
}
}
}
//合并2个有序子序列
void Merge(int array[], int temparray[], int head1, int head2, int rightend) {
int leftend = head2 - 1;
int i = head1, j = head2, k = head1;
//循环条件为要合并的两个小数组都不空
while (i <= leftend && j <= rightend)
{
if (array[i] <= array[j]) {
temparray[k++] = array[i];
i++;
}
else {
temparray[k++] = array[j];
j++;
}
}
//将剩余元素填入
while (i <= leftend)
{
temparray[k++] = array[i++];
}
while (j <= rightend)
{
temparray[k++] = array[j++];
}
//将临时数组中的元素移回原数组
for (i = head1; i <= rightend; i++)
{
array[i] = temparray[i];
}
}
//按长度length归并
void Merge_Pass(int array[], int temparray[], int n, int length) {
int i;
//完全的两两合并
for (i = 0; i <= n - 2 * length; i += 2 * length)
{
Merge(array, temparray, i, i + length, i + 2 * length - 1);
}
//残缺的两两合并
if (i + length < n) {
Merge(array, temparray, i, i + length, n - 1);
}
}
int main() {
int array1[MAXSIZE];
int array2[MAXSIZE];
int n;
//read array
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &array1[i]);
}
for (int i = 0; i < n; i++)
{
scanf("%d", &array2[i]);
}
//check insertion or heap sort
int p;
for (p = 0; p < n - 1; p++)
{
if (array2[p] > array2[p + 1]) {
p++;
break;
}
}
bool isinsertion = true;
for (int i = p; i < n; i++)
{
if (array1[i] != array2[i]) {
isinsertion = false;
break;
}
}
//get next step sort
if (isinsertion) {
int temp = array2[p];
int j = 0;
for (j = p; j > 0 && array2[j - 1] > temp; j--)
{
array2[j] = array2[j - 1];
}
array2[j] = temp;
printf("Insertion Sort\n");
PrintArray(array2, n);
}
else {
bool con = true;
int i;
for (i = 2; con ; i*=2)
{
for (int j = i; j < n; j+=i*2)
{
if (array2[j - 1] > array2[j]) {
con = false;
}
}
}
Merge_Pass(array2, array1, n, i/2);
printf("Merge Sort\n");
PrintArray(array2, n);
}
}
09-排序3 Insertion or Heap Sort (25point(s))
According to Wikipedia:
Insertion sort iterates, consuming one input element each repetition, and growing a sorted output list. Each iteration, insertion sort removes one element from the input data, finds the location it belongs within the sorted list, and inserts it there. It repeats until no input elements remain.
Heap sort divides its input into a sorted and an unsorted region, and it iteratively shrinks the unsorted region by extracting the largest element and moving that to the sorted region. it involves the use of a heap data structure rather than a linear-time search to find the maximum.
Now given the initial sequence of integers, together with a sequence which is a result of several iterations of some sorting method, can you tell which sorting method we are using?
Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤100). Then in the next line, N integers are given as the initial sequence. The last line contains the partially sorted sequence of the N numbers. It is assumed that the target sequence is always ascending. All the numbers in a line are separated by a space.
Output Specification:
For each test case, print in the first line either "Insertion Sort" or "Heap Sort" to indicate the method used to obtain the partial result. Then run this method for one more iteration and output in the second line the resulting sequence. It is guaranteed that the answer is unique for each test case. All the numbers in a line must be separated by a space, and there must be no extra space at the end of the line.
Sample Input 1:
Sample Output 1:
Sample Input 2:
Sample Output 2:
题解:
根据插入排序的性质,可以很容易判断出插入排序。
首先从0开始扫描,直到扫描到逆序的点,接着从逆序点p开始向后扫描到n-1,如果array1和array2都一样,说明一定是插入排序,否则为堆排序。
对插入排序,再对逆序的位置插一次。
对堆排序,先从后扫描直到小于堆顶,交换2个元素,并且调整堆,即可获得下一次结果。
#include<algorithm>
#include<cstdbool>
#include<cstdio>
#define MAXSIZE 101
using namespace std;
//调整堆
void Adjustment(int array[], int n, int k) {
//下标K代表数组元素array[k]空缺
int temp = array[k];
for (int j = k * 2 + 1; j < n; j = 2 * j + 1)
{
if (j + 1 < n && array[j + 1] > array[j]) {
j++;
}
if (array[j] > temp) {
array[k] = array[j];
k = j;
}
else {
break;
}
}
array[k] = temp;
}
//打印数组
void PrintArray(int arr[],int n) {
bool isf = true;
for (int i = 0; i < n; i++)
{
if (isf) {
printf("%d", arr[i]);
isf = false;
}
else {
printf(" %d", arr[i]);
}
}
}
int main() {
int array1[MAXSIZE];
int array2[MAXSIZE];
int n;
//read array
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &array1[i]);
}
for (int i = 0; i < n; i++)
{
scanf("%d", &array2[i]);
}
//check insertion or heap sort
int p;
for (p = 0; p < n-1; p++)
{
if (array2[p] > array2[p + 1]) {
break;
}
}
bool isinsertion = true;
for (int i = p+1; i < n; i++)
{
if (array1[i] != array2[i]) {
isinsertion = false;
break;
}
}
//get next step sort
if (isinsertion) {
int temp = array2[p+1];
int j = 0;
for (j = p + 1; j > 0 && array2[j - 1] > temp; j--)
{
array2[j] = array2[j - 1];
}
array2[j] = temp;
printf("Insertion Sort\n");
}
else {
for (int i = n-1; i >= 0; i--)
{
if (array2[0] > array2[i]) {
int temp = array2[i];
array2[i] = array2[0];
array2[0] = temp;
Adjustment(array2, i, 0);
break;
}
}
printf("Heap Sort\n");
}
PrintArray(array2, n);
}