排序效率一览:
本章介绍了五种常见排序方法,每种方法各有利弊。
1.冒泡排序
一个指针一开始指向最后一个位置,将之前两两比较出来的最大的那个值一步步交换到这个位置上。并且加入监控是否全程没有发生交换的变量flag,可以提前终止循环,代表完成了排序。
/*冒泡排序*/
void Bubble_sort(long a[], int N)
{
int flag;//一趟冒泡是否发生交换
for (int P = N - 1; P >= 0; P--)
{
flag = 0;
for (int i = 0; i < P; i++)
{
if (a[i] > a[i + 1]) {
swap(a[i],a[i+1]);
flag = 1;
}
if (flag = 0) //全程无交换,跳出循环
break;
}
}
}
用几组特征如下的数据来检测排序算法效率(下例也一样):
数据1:只有1个元素;
数据2:11个不相同的整数,测试基本正确性;
数据3:103个随机整数;
数据4:104个随机整数;
数据5:105个随机整数;
数据6:105个顺序整数;
数据7:105个逆序整数;
数据8:105个基本有序的整数;
数据9:105个随机正整数,每个数字不超过1000。
冒泡排序效率:

2.插入排序
相当于打扑克的排序,每次新摸一张牌,就从手牌的末尾处依次比较过去并腾出一格的位置,直到找到合适的位置完成一次插入。
/*插入排序*/
void Insertion_sort(long a[], int N)
{
for (int P = 1; P < N; P++)
{
long Tmp;
int i;
Tmp = a[P];
for ( i = P; i > 0 && Tmp < a[i - 1] ; i--)
a[i] = a[i - 1];
a[i] = Tmp;
}
}
插入排序效率

3.希尔排序
升级版的插入排序,以某种特定的间隔来依次进行插入,在某种情况下有助于提高效率。
/*希尔排序,间隔插入排序*/
void Shell_sort(long a[], int N)
{
int Si, D, P, i;
long Tmp;
//使用sedgewick间隔增量
int Sedgewick[] = { 929, 505, 209, 109, 41, 19, 5, 1, 0 };
for (Si = 0; Sedgewick[Si] >= N; Si++)
;//一开始的间隔量小于排序总数
for(D = Sedgewick[Si]; D>0 ; D = Sedgewick[++Si])
for (P = D; P < N; P++)
{
Tmp = a[P];
for (i = P; i >= D && Tmp < a[i - D]; i -= D) //注意是i-D而不是i-1 并且是i>D而不是i>0
a[i] = a[i - D];
a[i] = Tmp;
}
}

4.堆排序
(若你要排从小到大序列)先构建一个最大堆(在数组中就是一个个整理子树,整理到根节点为止),然后将堆顶最大元素和当前堆的最后一个元素交换位置。最后将最后一个元素整个踢出堆(也就是将堆的规模缩小一位)。
/*堆排序*/
void Swap(long *a, long *b)
{
long t = *a;
*a = *b;
*b = t;
}
void PerDown(long a[], int p, int N)//向下过滤函数
{
int Parent, Child;
long X;
X = a[p]; //取出根节点的值
for (Parent = p; (Parent * 2 + 1) < N; Parent = Child)
{
Child = Parent * 2 + 1;//因为从0开始的,所以左儿子要加一
if ((Child != N - 1) && (a[Child] < a[Child + 1]))
Child++;//child指向左右儿子中较大者
if (X >= a[Child])
break; //若根节点大于此时的最大的儿子,则找到合适位置
else
a[Parent] = a[Child];
}
a[Parent] = X;
}
void HeapSort(long a[],int N)
{
int i;
/*从0开始的树,所以最后一个父节点是N/2-1,把10带进去很好理解。
建树的时候所有点都是乱的,所以每个子树都要向下过滤调整,
但交换后就不一样了,只有堆顶的元素是乱的,所以不用写循环了*/
for (i = N / 2 - 1; i >= 0; i--)
PerDown(a, i ,N );//向下过滤,整理树
for (i = N - 1; i > 0; i--)//每循环一次缩小一次堆的规模
{
Swap(&a[0], &a[i]);//交换堆顶和最后一个元素
PerDown(a, 0, i);
}
c++背诵版:
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
HeapSort(nums, n);
return nums;
}
void adjust(vector<int> &nums, int len, int index) {
int left = 2 * index + 1; // index的左子节点
int right = 2 * index + 2;// index的右子节点
int maxIdx = index;
if (left<len && nums[left] > nums[maxIdx]) maxIdx = left;
if (right<len && nums[right] > nums[maxIdx]) maxIdx = right;
if (maxIdx != index)//有交换需求
{
swap(nums[maxIdx], nums[index]);
adjust(nums, len, maxIdx); //向下过滤
}
}
// 堆排序
void HeapSort(vector<int> &nums, int size) {
for (int i = size / 2 - 1; i >= 0; i--) //从倒数第一个有儿子的父节点开始调整,一直调整到root
{
adjust(nums, size, i);
}
for (int i = size - 1; i >= 1; i--) {
swap(nums[0], nums[i]);//最大的哥们放到最后面,并且堆规模减一
adjust(nums, i, 0);//继续调整
}
}
};

5.归并排序
算法核心是有序子列的归并Merge。也就是将左右两个子列分别比较每一个元素大小,将较小的填入Tmp数组中,然后将贡献出最小元素的那个原始数组的指针向后移一位。
然后有两种写法,递归和迭代。
递归归并:
/*归并排序*/
//有序子列归并函数
void Merge(long a[], long Tmpa[], int L, int R, int RightEnd)
{
int LeftEnd;
int Tmp; //归并后数组指针
int NumElements; //记录左右子列元素总数
LeftEnd = R - 1;//左右子列紧挨着
Tmp = L;//对齐Tmp数组和左数组
NumElements = RightEnd - L + 1;
while (L <= LeftEnd && R <= RightEnd)
{
if (a[L] <= a[R])
Tmpa[Tmp++] = a[L++]; //L或者R在赋值后要记得也要移动
else
Tmpa[Tmp++] = a[R++];
}
while (L <= LeftEnd) //若一边的数组有多出来的元素
Tmpa[Tmp++] = a[L++];
while (R <= RightEnd)
Tmpa[Tmp++] = a[R++];
for (int i = 0; i < NumElements; i++, RightEnd--)
a[RightEnd] = Tmpa[RightEnd];
}
//递归归并,分而治之
void Msort(long a[], long Tmpa[], int L, int RightEnd)
{
int Center;//一直递归对半分,分而治之
if (L < RightEnd) //注意是小于,而不是小于等于,小于号才保证递归的部分里面有元素
{
Center = (L + RightEnd) / 2;
Msort(a, Tmpa, L, Center); //对左边递归排序
Msort(a, Tmpa, Center + 1, RightEnd);//对右边递归排序
Merge(a, Tmpa, L, Center + 1, RightEnd);//把左右归并
}
}
//统一接口
void Merge_sort(long a[], int N)
{
long* Tmpa;//指向long的指针Tmpa(实际上就是Tmpa数组)
Tmpa = (long*)malloc(N * sizeof(long));
if (Tmpa != NULL)//能申请到空间
{
Msort(a, Tmpa, 0, N - 1);
free(Tmpa); //记得释放空间
}
else
cout << "No Space";
}
归并效率:

另外还有循环归并:
/* 归并排序 - 循环实现 */
/* 这里Merge函数在递归版本中给出 */
/* length = 当前有序子列的长度*/
void Merge_pass( ElementType A[], ElementType TmpA[], int N, int length )
{ /* 两两归并相邻有序子列 */
int i, j;
for ( i=0; i <= N-2*length; i += 2*length )
Merge( A, TmpA, i, i+length, i+2*length-1 );
if ( i+length < N ) /* 归并最后2个子列*/
Merge( A, TmpA, i, i+length, N-1);
else /* 最后只剩1个子列*/
for ( j = i; j < N; j++ ) TmpA[j] = A[j];
}
void Merge_Sort( ElementType A[], int N )
{
int length;
ElementType *TmpA;
length = 1; /* 初始化子序列长度*/
TmpA = malloc( N * sizeof( ElementType ) );
if ( TmpA != NULL ) {
while( length < N ) {
Merge_pass( A, TmpA, N, length );
length *= 2;
Merge_pass( TmpA, A, N, length );
length *= 2;
}
free( TmpA );
}
else printf( "空间不足" );
}
例二:09-排序2 Insert or Merge (25分)
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:
10
3 1 2 8 7 5 9 4 6 0
1 2 3 7 8 5 9 4 6 0
Sample Output 1:
Insertion Sort
1 2 3 5 7 8 9 4 6 0
Sample Input 2:
10
3 1 2 8 7 5 9 4 0 6
1 3 2 8 5 7 4 9 0 6
Sample Output 2:
Merge Sort
1 2 3 8 4 5 7 9 0 6
思路: 题目大意是让你判断一个排序排了一半的结果,是用的插入排序还是归并排序。首先观察数组最大值N才100,所以可以将排序算法每迭代一次进行一次判断,如果发现某次循环的时候结果完全相同,就可以下结论是某种排序方法了。然后再将这种循环再进行一次,就可以输出结果了。BTW,因为要在循环上做文章,所以需要将归并排序用循环的方式写出来。
需要注意的是:
1.这题主要在考两种排序方式的改写。特别是在最后的输出的过程中,必须搞清楚插入循环中的P(摸的新的那张牌,故下次执行从上次输出的P+1开始),以及归并循环中的length(当前有序的子序列长度,故下次执行从上次输出的length开始)。
2.在Merge_sort中,要想判断正确是否相同,要注意归并循环程序是在A和TmpA中来回互相倒。因此要注意IsSame的比较对象。并且,要记得如果是奇数次数的循环,TmpA中存放得才是奇数次时正确的值,要把TmpA整个复制到A上面(因为最后输出接口是A)。
#include <stdio.h>
#include <iostream>
using namespace std;
const int MaxNumber = 101;
//Insert排序直接在origin上排,meger在MergeA上排序
int OriginA[MaxNumber], MergeA[MaxNumber];
int TargetA[MaxNumber];
int flag = 0;//用来区分是哪种排序的flag,1为插入,2为合并
bool IsSame(int Origin[], int Target[],int N)
{
for (int i = 0; i < N; i++)
{
if (Origin[i] != Target[i])
return false;
else
continue;
}
return true;
}
/*插入排序*/
int Insertion_sort(int A[],int N,int Times)
{
int P ;
if (flag == 1)
P = Times+1;//二次执行时,摸后面一张牌
else
P = Times;
for ( P ; P < N; P++)
{
int Tmp;
int i;
Tmp = A[P];
for (i = P; i > 0 && Tmp < A[i - 1]; i--)
A[i] = A[i - 1];
A[i] = Tmp;
if (flag == 1)//若flag等于1,则说明是第二次调用排序
return P; //跑一轮就直接强行结束
if (IsSame(OriginA, TargetA, N)) {
flag = 1;//如果相同则改变flag
return P;
}
}
return P;
}
/*迭代合并排序*/
void Merge(int A[], int TmpA[], int L, int R, int RightEnd)
{
int LeftEnd, NumerElements,Tmp;
LeftEnd = R - 1;
NumerElements = RightEnd - L + 1;
Tmp = L;
while (L <= LeftEnd && R<=RightEnd)
{
if (A[L] <= A[R])
TmpA[Tmp++] = A[L++];
else
TmpA[Tmp++] = A[R++];
}
while (L<=LeftEnd)
{
TmpA[Tmp++] = A[L++];
}
while (R <= RightEnd)
{
TmpA[Tmp++] = A[R++];
}
}
//一趟归并
void Merge_pass(int A[], int TmpA[] , int N , int length)
{
int i;
for ( i = 0; i <= N - 2 * length; i += 2 * length)
Merge(A, TmpA, i, i + length, i + 2 * length - 1);
if (i + length < N)//若末尾还剩下一个多的位置空间,可以分为两个子数组
Merge(A, TmpA, i, i + length, N - 1);//照常合并
else
{
for (int j = i; j < N; j++)//若末尾只剩一个子数组的空间,则一一复制过去
TmpA[j] = A[j];
}
}
//统一接口,多次归并
int Merge_sort(int A[], int N , int length)
{
int NotFirstTime = 0;
int *TmpA;
TmpA = (int* )malloc( sizeof(int)*N);
if (flag == 2)//设置一个新监控变量,因为while内部如果继续用flag可能直接跳出函数
NotFirstTime = 1;//不需要改变length。length的意义是当前有序子序列长度。
if (TmpA != NULL)
{
while (length < N)
{
Merge_pass(A, TmpA, N, length);
length *= 2;
//此轮排序存储在TmpA里
if (IsSame(TmpA, TargetA, N)) {
flag = 2;//如果相同则改变flag
for (int i = 0; i < N; i++)//奇数次数运算后,记得把TmpA中的复制回A
A[i] = TmpA[i];
return length;
}
/*若不是第一次执行,则说明是第二次调用排序*/
if (NotFirstTime == 1) {
for (int i = 0; i < N; i++)//把TmpA中数值拷贝回A中
A[i] = TmpA[i];
return length;
} //跑一轮就直接强行结束
Merge_pass(TmpA, A, N, length);
length *= 2;
//此轮排序存储在A里面
if (IsSame(A, TargetA, N)) {
flag = 2;//如果相同则改变flag
return length;
}
}
free(TmpA);
}
else
cout << "no enough space";
return length;
}
int main()
{
int N;
cin >> N;
for (int i = 0; i < N; i++)//读取原始数组
{
cin >> OriginA[i];
MergeA[i] = OriginA[i];//同时复制一份
}
for (int i = 0; i < N; i++)//读入目标数组
{
cin >> TargetA[i];
}
int Times,length;
Times = Insertion_sort(OriginA, N,1);//初始牌号给1
length = Merge_sort(MergeA, N,1);//初始有序子数组的长度给1
//如果是insert排序
if (flag == 1) {
cout << "Insertion Sort" << "\n";
Insertion_sort(OriginA, N,Times);
for (int i = 0; i < N; i++)
{
if (i != N - 1)
cout << OriginA[i] << " ";
else
cout << OriginA[i];
}
}
//如果是merge排序
if (flag == 2) {
cout << "Merge Sort" << "\n";
Merge_sort(MergeA, N, length);
for (int i = 0; i < N; i++)
{
if (i != N - 1)
cout << MergeA[i] << " ";
else
cout << MergeA[i];
}
}
return 0;
}
例三:09-排序3 Insertion or Heap Sort (25分)
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:
10
3 1 2 8 7 5 9 4 6 0
1 2 3 7 8 5 9 4 6 0
Sample Output 1:
Insertion Sort
1 2 3 5 7 8 9 4 6 0
Sample Input 2:
10
3 1 2 8 7 5 9 4 6 0
6 4 5 1 0 3 2 7 8 9
Sample Output 2:
Heap Sort
5 4 3 1 0 2 6 7 8 9
思路: 和上面那题思路几乎一模一样,只不过由归并排序改成了堆排序。只需要注意将堆排序规模减少的量返回出来,然后每次循环增加一次这个“规模减少量”就可以了。
需要注意的是:
堆排序模板代码本身要记清,最后交换堆顶和堆最后一个元素,那里的之后的调整只需要调整堆顶就可以了,因为只有堆顶是无序的。
#include <stdio.h>
#include <iostream>
using namespace std;
const int MaxNumber = 101;
//Insert排序直接在origin上排,heap在HeapA上排序
int OriginA[MaxNumber], HeapA[MaxNumber];
int TargetA[MaxNumber];
int flag = 0;//用来区分是哪种排序的flag,1为插入,2为堆排序
bool IsSame(int Origin[], int Target[],int N)
{
for (int i = 0; i < N; i++)
{
if (Origin[i] != Target[i])
return false;
else
continue;
}
return true;
}
/*插入排序*/
int Insertion_sort(int A[],int N,int Times)
{
int P ;
if (flag == 1)
P = Times+1;//二次执行时,摸后面一张牌
else
P = Times;
for ( P ; P < N; P++)
{
int Tmp;
int i;
Tmp = A[P];
for (i = P; i > 0 && Tmp < A[i - 1]; i--)
A[i] = A[i - 1];
A[i] = Tmp;
if (flag == 1)//若flag等于1,则说明是第二次调用排序
return P; //跑一轮就直接强行结束
if (IsSame(OriginA, TargetA, N)) {
flag = 1;//如果相同则改变flag
return P;
}
}
return P;
}
/*插入排序结束*/
/*堆排序*/
void Swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
void PerDown(int A[], int p, int N)
{
int Parent, Child;
int X;
X = A[p];//取出根节点
for (Parent = p; (Parent * 2 + 1) < N; Parent = Child)
{
Child = Parent * 2 + 1;//数组从0开始,故p*2+1才是左儿子
if ((Child != N - 1) && (A[Child]) < A[Child + 1])
Child++;
if (X >= A[Child])
break;
else
A[Parent] = A[Child];
}
A[Parent] = X;
}
int Heap_sort(int A[] ,int N , int ReduceNodes)
{
int i;
if(flag != 2)
for (i = N / 2 - 1; i >= 0; i--)//建最大堆
PerDown(A, i, N);
for (i = N - ReduceNodes; i > 0; i--)//循环一次缩小一次规模
{
Swap(&A[0], &A[i]);//交换堆顶和最后一个元素
PerDown(A, 0, i);
ReduceNodes++;//下一次需要再减少一个规模
if (flag == 2)//若是第二次执行,直接跳出
return ReduceNodes;
if (IsSame(HeapA, TargetA, N))//若发现相等,改变flag
{
flag = 2;
return ReduceNodes;
}
}
return ReduceNodes;
}
/*堆排序结束*/
int main()
{
int N;
cin >> N;
for (int i = 0; i < N; i++)//读取原始数组
{
cin >> OriginA[i];
HeapA[i] = OriginA[i];//同时复制一份
}
for (int i = 0; i < N; i++)//读入目标数组
{
cin >> TargetA[i];
}
int Times , ReduceNodes;
Times = Insertion_sort(OriginA, N,1);//初始牌号给1
ReduceNodes = Heap_sort(HeapA, N, 1);
//如果是insert排序
if (flag == 1) {
cout << "Insertion Sort" << "\n";
Insertion_sort(OriginA, N,Times);
for (int i = 0; i < N; i++)
{
if (i != N - 1)
cout << OriginA[i] << " ";
else
cout << OriginA[i];
}
}
//如果是heap排序
if (flag == 2) {
cout << "Heap Sort" << "\n";
Heap_sort(HeapA, N, ReduceNodes);
for (int i = 0; i < N; i++)
{
if (i != N - 1)
cout << HeapA[i] << " ";
else
cout << HeapA[i];
}
}
return 0;
}
本文详细介绍了五种排序算法:冒泡排序、插入排序、希尔排序、堆排序和归并排序,包括它们的工作原理和效率。并通过示例数据展示了每种排序算法的运行情况。此外,还提供了如何根据部分排序结果判断使用的是插入排序还是归并排序的问题,以及解题思路和注意事项。
760

被折叠的 条评论
为什么被折叠?



