五大排序算法及题目汇总
冒泡排序
冒泡排序的本质在于交换,即每次通过交换的方式把当前剩余元素的最大值移动到一端,而当剩余元素减少为0时,排序结束。冒泡排序(Bubble Sort)是一种简单直观的排序算法。
步骤:
-
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
-
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
-
针对所有的元素重复以上的步骤,除了最后一个。
-
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
代码实现:
void bubbleSort(int arr[])
{
for (int i = 0; i < n - 1; i ++ )
{
for (int j = i; j < n - 1; j ++ )
{
if(arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
选择排序
选择排序是最简单的排序算法之一,主要介绍众多选择排序方法中最常用的简单选择排序。如图所示,简单选择排序是指,对一个序列A中的元素A[0]~A[n-1],令i从0到n枚举,进行n趟操作,每趟从待排序部分[i,n-1]中选择最小的元素,令其与待排序部分的第一个元素A[i]进行交换,这样元素A[i]的就会与当前有序区间[1,i-1]形成新的有序区间[1,i]。于是在n趟操作后,所有元素就会是有序的。
步骤:
-
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
-
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
-
重复第二步,直到所有元素均排序完毕。
代码实现:
void selectSort(int arr[])
{
for (int i = 0; i < n; i ++ )
{
int min = i; //令最小下标为min
for (int j = i + 1; j < n; j ++ )
{
if(arr[min] > arr[j])
{
min = j; //更新最小下标
}
}
//交换
int temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}
插入排序
插入排序也是最简单的一类排序方法,该算法与打扑克整理手牌顺序的原理类似。本节主要介绍众多插入排序方法中最直观的直接插入排序。直接插入排序是指,对序列A的n个元素A[0]~A[n-1],令i从1到n-1枚举,进行
n-1趟操作。假设某一趟时,序列A的前i-1个元素A[0]~A[i-1]已经有序,而范围[i,n-1]
还未有序,那么该趟从范围[0,i-1]中寻找某个位置j,使得将A[i]插入位置j后(此时A[j]~
A[i-1]会后移一位至A[j+1]~A[i]),范围[0,i]有序。
步骤:
-
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
-
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
代码实现:
void insertSort(int arr[], int n)
{
for (int i = 1; i < n; i ++ )
{
int temp = arr[i], j = i;
while(j > 0 && temp < arr[j - 1])
{
arr[j] = arr[j - 1]; //arr[j-1]后移
j--;
}
arr[j] = temp; //插入操作
}
}
归并排序
双指针扫描法
此算法用于引入双指针的思想,该思想将同样用于后面的序列归并问题以及归并排序算法。
例:
给定一个递增的正整数序列和一个正整数M,求序列中的两个不同位置的数a和b,使得它们的和恰好为M,输出所有满足条件的方案。例如给定序列arr[]={1,2,3,4,5,6}和正整数M=8,就存在2+6=8与3+5=8成立。
分析:
最直接的方法是进行双重遍历,但其时间复杂度是O(n2),n超过105时就不可取了。所以采用时间复杂度为O(n)的双指针扫描算法。该算法使用两个数组下标变量i、j,分别指向数组的第一个和最后一个元素,下标i递增,下标j递减。注意这是一个递增序列,下面进一步分析:
- 当arr[i]+arr[j]=M时,打印结果并且i,j下标同时移动。
- 当arr[i]+arr[j]<M时,说明arr[i]+arr[j-1]<M也成立,此时要移动i下标,增大其相加的值。
- 当arr[i]+arr[j]>M时,说明arr[i]+arr[j-1]>M也成立,此时要移动j下标,减小其相加的值。
代码实现:
while(i < j)
{
if(arr[i] + arr[j] == M)
{
printf("%d %d\n", arr[i], arr[j]);
i++;
j--;
}
else if(arr[i] + arr[j] < M)
{
i++;
}
else
{
j--;
}
}
此时i,j的总移动次数不会超过n,因此时间复杂度也就为O(n)。可见此算法避免了一些不必要遍历,及时剪枝,大大缩小了时间复杂度。
序列合并(merge)
例:
假设有两个递增序列A与B,要求将它们合并为一个递增序列C。
分析:
同样的,可以设置两个下标i和j,初值均为0,表示分别指向序列A的第一个元素和序列B的第一个元素,然后根据A[i]与B[j]的大小来决定哪一个放入序列C。
- 若A[i]的<B[j],说明A[i]的是当前序列A与序列B的剩余元素中最小的那个,因此把A[i]加入序列C中,并让i加1(即让i右移一位)。
- 若A[i]>B[j],说明B是当前序列A与序列B的剩余元素中最小的那个,因此把
B们加入序列C中,并让j加1(即让j右移一位)。 - 若A[i]=B[j],则任意选一个加入到序列C中,并让对应的下标加1。
上面的分支操作直到i、j中的一个到达序列末端为止,然后将另一个序列的所有元素依次加入序列C中。
代码实现:
int merge(int A[], int B[], int C[], int n, int m)
{
int i = 0, j = 0, index = 0;
while(i < n && j < m)
{
if(A[i] < B[j])
C[index++] = A[i++];
else
C[index++] = B[j++];
}
//将剩余元素加入序列C中
while(i < n) C[index++] = A[i++];
while(j < n) C[index++] = B[j++];
return index; //返回序列C的长度
}
归并排序
归并排序主要讨论2路归并排序,例如将序列A[]={66,12,33,57,64,27,18}进行归并排序,如下图所示,其主要思想是将序列两两分组直到只剩下一组,组内进行单独排序。归并排序的时间复杂度是O(nlogn)。
代码实现:
首先先写出每组的归并排序函数merge,其实现方式和以上介绍的序列合并基本相同。
数组A的左区间范围是[L1,R1],右区间范围是[L2,R2],其中L2=R1+1
const int max = 100;
void merge(int A[], int L1, int R1, int L2, int R2)
{
int i = L1, j = L2;
int temp[max], index = 0;
while(i <= R1 && j <= R2)
{
if(A[i] < A[j])
temp[index++] = A[i++];
else
temp[index++] = A[j++];
}
while(i <= R1) temp[index++] = A[i++];
while(j <= R2) temp[index++] = A[j++];
for(int i = 0; i < index; i++)
{
A[L1 + i] = temp[i];
}
}
接下来写出对整个序列进行2路归并排序的递归代码
void mergeSort(int A[], int left, int right)
{
if(left < right)
{
int mid = (left + right) / 2;
mergeSort(A, left, mid);
mergeSort(A, mid + 1, right);
merge(A, left, mid, mid + 1, right); //左子区间与右子区间合并
}
}
快速排序
快速排序是排序算法中平均时间复杂度为O(nlogn)的一种算法,其实现需要先解决这样一个问题:对一个序列A[1]、A[2]、…、A[n],调整序列中元素的位置,使得A1的左侧所有元素都不超过A[1]、右侧所有元素都大于A[1]。例如对序列{5,3,9,6,4,1}来说,可以调整序列中元素的位置,形成序列{3,1,4,5,9,6},这样就让A[1]=5左侧的所有元素都不超过它、右侧的所有元素都大于它。
步骤:
-
从数列中挑出一个元素,称为 “基准”(pivot);
-
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
-
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
分区代码:
int partition(int arr[], int left, int right)
{
int temp = arr[left]; //将arr[left]作为主元
while(left < right)
{
while(left < right && arr[right] > temp) right--;
arr[left] = arr[right];
while(left < right && arr[left] <= temp) left++;
arr[right] = arr[left];
}
arr[left] = temp;
return left; //返回相遇的位置
}
快排代码:
void quickSort(int arr[], int left, int right)
{
if(left < right)
{
int pos = partition(arr, left, right);
quickSort(arr, left, pos - 1); //左区间递归进行快排
quickSort(arr, pos + 1, right); //右区间递归进行快排
}
}