排序算法
一、内部排序
1.冒泡排序:
外循环: 0 <= i <= n - 1
内循环:r [ j ] 和 r [ j + 1 ] 比较(j >= 0 && j < n - 1 - i )
0.0
内外循环控制循环次数:首先内循环比较的是 0 和 1 ,1 和 2……n-2 和 n - 1进行比较,将较大值冒泡到后面,最终第 n - 1 位置是最大值,内循环的范围是 0 到 n - 1。
0.0
接下来的内循环的次数就发生变化了,变成 0 到 n - 2了,因为最后一个是最大了,因而需要确定第 2 大的数字,比较的方法还是一样。
0.0
最终,比较 0 和 1 位置的元素值,将较大的放在 1 的位置,完成整个算法过程。
循环次数\下标 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
1 | 5 | 44 | 23 | 24 | 18 |
2 | 5 | 23 | 44 | 24 | 18 |
3 | 5 | 23 | 24 | 44 | 18 |
4 | 5 | 23 | 24 | 18 | 44 |
5 | 5 | 23 | 24 | 18 | 44 |
6 | 5 | 23 | 18 | 24 | 44 |
7 | …… |
0.0
下面算法中,还提供了一个标志:sorted
当第一次看到这个sorted就深深被它吸引,这个标志避免了不需要的比较过程:我们每次打算开始比较过程之前,都设置一个标志sorted = true,只有当交换之后sorted才为false。假如,某次循环过程中,sorted 为true了,说明交换过程没有发生,说明所有的数字都已经排好序了,一个交换都没有,从而停止了外层循环,减少了不必要的比较。
void BubbleSort(int a[],int n)
{
int temp;
bool sorted = false;
for(int i = 0; i < n && sorted == false;n--)
for(int j = 0;j< n-i-1;j++)
{
sorted = true;
if( a[j] >a[j+1])
{
temp = a[j] ;
a[j] = a[j+1];
a[j+1] = temp;
sorted = false;
}
}
}
换一种写法:
void bubblesort(int A[],int n)
{
bool sorted = false;
while(!sorted)
{
sorted = true;
for(int i = 1;i < n;++i)
if(A[i-1]> A[i])
{
swap(A[i-1],A[i]);
sorted = false;
}
--n;
}
}
再换一种写法:
void bubblesort(int A[],int n)
{
for(bool sorted = false; sorted = !sorted;--n)
{
sorted = true;
for(int i = 1;i < n;++i)
{
if(A[i-1] > A[i])
{
swap(A[i-1],A[i]);
sorted = false;
}
}
}
}
算法的最好情况:
只需要一趟循环:0 和 1 ,1 和 2……n - 2 和 n - 1,总共是n - 1次,O(n)。
最坏的情况:需要比较 n - 1,n - 2,n - 3,…… 1 次,总共是n (n - 1)/2,O(n^2)。
2.选择排序:
从第一个数开始,和剩下没比较过的数相比,比它小就交换值,知道比到n-2。
void select(int a[],int n)
{
int temp;
for(int i = 0;i<n-1;i++)
for(int j = i+1;j<n;j++)
if(a[i] > a[j])
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
O(n*n)
3.插入排序:
3.1直接插入排序
0.0
顾名思义,直接二字,我们假定原序列是一个排序好的(假定从小到大),要将新的数字插入,那么他的前一个数字一定比他小,后一个数字一定比他大。
0.0
定义很抽象,为什么能假定序列是排好序的?我们不妨将假定序列从1个数字开始。
原序列 | 55 | 17 | 34 | 77 | 98 | 5 | 过程 |
---|---|---|---|---|---|---|---|
插入1 | 17 | 55 | 34 | 77 | 98 | 5 | 17插到55前 |
插入2 | 17 | 34 | 55 | 77 | 98 | 5 | 34插到55前 |
插入3 | 5 | 17 | 34 | 55 | 77 | 98 | 5插到17前 |
0.0
算法的过程可以分为以下几步:
- 从位置 1 的数字开始,与前一个数字比较,如果比前一个数字小,则需要将 1 位置元素插入到 0 的前面,将 0 位置的元素后移到1的位置。我们很容易看到,插入的过程也同时带着数组元素后移的问题。
- 位置 2 的数字和位置 1 的数字比较,如果大于它,说明顺序正确;如果比 1 的数字小,那么就需要插入了,需要找到第一个比a[ i ]小的数字,将a[2]插入到这个数字的后面,将后面所有的元素后移一位。
- 其实插入2和插入3两个过程中也有好几次比较,只是不用插入罢了。
void InsertSort(int a[],int n)
{
for (int i = 1; i < n; i++)
{
int temp = a[i];
int j;
for (j = i - 1; j >= 0 && temp < a[j]; j--)
//最终是遇到了一个temp > a[j]的情况,就将temp赋值到a[j+1]
a[j + 1] = a[j]; //将元素后移一位
a[j + 1] = temp;
}
}
最好情况:
原序列正序的情况下,完全不需要插入和移动,这时候只需要比较n - 1次,复杂度为O(n)
原序列逆序的情况下,每次都需要移动和插入,每次的比较的数量达到最大值,( n + 2 )( n - 1 ) / 2,复杂度为O( n ^ 2 )。
3.2折半插入
利用了之前的折半查找的知识,折半查找,最终找到mid值等于需要查找的value,不难推导,循环结束之后,high+1的值永远大于value。high的值永远小于等于value。
void BInsertSort(int a[],int n)
{
int temp,low,high;
int mid;
for (int i = 1;i < n;i++)
{
temp = a[i];
low = 0,high = i - 1;
while (low<=high)
{
mid = (low+high)/2;
if ( temp < a[mid])
high = mid - 1;
else
low = mid + 1;
}
for(int j = i - 1;j>=high + 1;--j)
a[j+1] = a[j];
a[high + 1] = temp;
}
}
3.3二路插入排序
4.希尔排序(插入排序改进)
void shellsort(int a[],int n)
{
int i,j,gap = n,temp;
do
{
gap = gap/3+1;
for(i = gap;i<n;i++)
{
gap = gap/3+1;
if ( a[i] < a[i-gap])
{
temp = a[i];
for ( j = i-gap;j >= 0 && temp < a[j]; j-=gap )
{
a[j+gap] = a[j];
}
a[j+gap] = temp;
}
}
} while (gap > 1);
}
5. 归并排序
#define MAXSIZE 10
// 实现归并,并把最后的结果存放到list1里
void merging(int *list1, int list1_size, int *list2, int list2_size)
{
int i, j, k, m;
int temp[MAXSIZE];
i = j = k = 0;
while( i < list1_size && j < list2_size )
{
if( list1[i] < list2[j] )
{
temp[k++] = list1[i++];
}
else
{
temp[k++] = list2[j++];
}
}
while( i < list1_size )
{
temp[k++] = list1[i++];
}
while( j < list2_size )
{
temp[k++] = list2[j++];
}
for( m=0; m < (list1_size + list2_size); m++ )
{
list1[m] = temp[m];
}
}
void MergeSort(int k[], int n)
{
if( n > 1)
{
int *list1 = k;
int list1_size = n/2;
int *list2 = k + n/2;
int list2_size = n - list1_size;
MergeSort(list1, list1_size);
MergeSort(list2, list2_size);
merging(list1, list1_size, list2, list2_size);
}
}
```c
#include <stdio.h>
#define MAXSIZE 10
// 实现归并,并把最后的结果存放到list1里
void merging(int *list1, int list1_size, int *list2, int list2_size)
{
int i, j, k, m;
int temp[MAXSIZE];
i = j = k = 0;
while( i < list1_size && j < list2_size )
{
if( list1[i] < list2[j] )
{
temp[k++] = list1[i++];
}
else
{
temp[k++] = list2[j++];
}
}
while( i < list1_size )
{
temp[k++] = list1[i++];
}
while( j < list2_size )
{
temp[k++] = list2[j++];
}
for( m=0; m < (list1_size + list2_size); m++ )
{
list1[m] = temp[m];
}
}
void MergeSort(int k[], int n)
{
if( n > 1)
{
int *list1 = k;
int list1_size = n/2;
int *list2 = k + n/2;
int list2_size = n - list1_size;
MergeSort(list1, list1_size);
MergeSort(list2, list2_size);
merging(list1, list1_size, list2, list2_size);
}
}
int main()
{
int i, a[10] = {5, 2, 6, 0, 3, 9, 1, 7, 4, 8};
MergeSort(a, 10);
printf("排序后的结果是:");
for( i=0; i < 10; i++ )
{
printf("%d", a[i]);
}
printf("\n\n");
return 0;
}
归并排序复杂度nlog(n)
6.快速排序
快速排序:利用递归与分治的思想。基本思路:选个key值,将所有小于key的数排到key的左边,所有大于key的数字排到key的右边,记录key的下标j,然后将数组分成两部分,继续这样的操作。
快排里有一些变量:
low:数组开始下标。
high:数组结尾下标。
key:关键数据。
0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
4 | 3 | 2 | 6 | 1 | 5 |
上图是一个需排序的数组a。
快排的基本思想:
限定条件:i < j
作用域左括号{
循环1:
key 赋值 a[0] = 4;
low = 0;high = 5;
从左到右,寻找比key大的数字,找到了 a[3] = 6,记录下标 i = 3 .。
0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
4 | 3 | 2 | 6 | 1 | 5 |
从右到左,寻找比key小的数字,找到了 a[4] = 1,记录下标 j = 4;
0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
4 | 3 | 2 | 6 | 1 | 5 |
交换 i 与 j 对应数组元素,即 a[3] 与 a[4]。
此时的数组:
0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
4 | 3 | 2 | 1 | 6 | 5 |
从i开始再向右,从左到右,寻找比key大的数字,找到了 a[4] = 6,记录下标 i = 4 .。
从j开始再向左,从右到左,寻找比key小的数字,找到了 a[3] = 1,记录下标 j = 3;
此时 i > j,跳出循环。
作用域右括号}
下面有了一步操作:
交换key值 a [ 0 ] 和 a [ j ]的值,这样就保证了key的左边,全是小于key的数字,key的右边,全是大于key的数字。
数组变成了这样:
0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
1 | 3 | 2 | 4 | 6 | 5 |
接下来就容易理解了:
将数组切成两半,0-j,j-high两部分,再进行循环操作,最终会形成一个两个数一组的比较大小,得到答案。结合代码看是这样:
#include <iostream>
using namespace std;
void Qsort(int arr[], int low, int high)
{
if (high <= low) return;
int i = low;
int j = high + 1;
int key = arr[low];
while (true)
{
while (arr[++i] < key)
{
if (i == high){
break;
}
}
while (arr[--j] > key)
{
if (j == low){
break;
}
}
if (i >= j) break;
/*交换i,j对应的值*/
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
int temp = arr[low];
arr[low] = arr[j];
arr[j] = temp;
Qsort(arr, low, j - 1);
Qsort(arr, j + 1, high);
}
int main()
{
int a[100000];
int n;
cin >> n;
for(int i =0;i<n;i++)
cin >> a[i];
Qsort(a, 0, n-1);
for(int i = 0; i < n ; i++)
{
cout << a[i] << " ";
}
return 0;
}
换一种写法
int Partition(int data[],int length,int start,int end){
if(data == nullptr || length <= 0 || start < 0 || end >= length)
return -1;
int index = RandomInRange(start,end);
swap(data[index],data[end]);
int small = start - 1;
for(index = start;index < end;++index){
if(data[index] < data[end]){
++small;
if(small != index)
swap(data[index],data[small]);
}
}
++small;
swap(data[index],data[small]);
return small;
}
void qsort(int data[],int length,int start,int end){
if(start == end)
return;
int index = Partition(data,length,start,end);
if(index > start)
qsort(data,length,start,index - 1);
if(index < end)
qsort(data,length,index + 1,end);
}
7.基数排序
二、外部排序
1.堆排序
基本思想:
1.将序列形成一个大顶堆或者小顶堆
2.根节点移到末尾
3.剩余的n-1个构成一个新的堆
反复进行形成有序序列
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;//堆排序的核心是建堆,传入参数为数组,根节点位置,数组长度
void Heap_adjust(int a[],int root,int len){
int lc = root * 2 + 1;//根节点的左子结点下标
if (lc < len){//左子结点下标不能超出数组的长度
int flag = lc;//flag保存左右节点中最大值的下标
int rc = lc + 1;//根节点的右子结点下标
if (rc < len)//右子结点下标不能超出数组的长度(如果有的话)
if (a[rc] > a[flag])//找出左右子结点中的最大值
flag = rc;
if (a[root] < a[flag]){
//交换父结点和比父结点大的最大子节点
swap(a[root],a[flag]);
//从此次最大子节点的那个位置开始递归建堆
Heap_adjust(a,flag,len);
}
}
}
void Heap_sort(int a[],int len){
for (int i = len / 2; i >= 0; --i)//从最后一个非叶子节点的父结点开始建堆
Heap_adjust(a,i,len);
for (int j = len-1; j > 0; --j){//j表示数组此时的长度,因为len长度已经建过了,从len-1开始
swap(a[0],a[j]);//交换首尾元素,将最大值交换到数组的最后位置保存
Heap_adjust(a,0,j);//去除最后位置的元素重新建堆,此处j表示数组的长度,最后一个位置下标变为len-2
}
}
int main(int argc, char **argv)
{
int a[10] = {12,45,748,12,56,3,89,4,48,2};
Heap_sort(a,10);
for (size_t i = 0; i != 10; ++i)
cout << a[i] << " ";
return 0;
}