// 尽量让文章通俗易懂//
排序生活中常常会用到,公司业级考核排名,学校成绩排名等等。
假如我们是一个小白,面对几百个杂乱的数字 如何让他变得有序呢?
我们会想到一个一个排序呗,先找到一个放在第一位,碰到比他大的放在 后面,比他小的放在前面。
或者在这些树中找出最小的放在第一个,接着找次小的,依次..................
这或许是解决问题的方法,但还有更高效的办法吗?
目录
_______________________________________________________________________________________________
一、基本 概念
那什么是排序呢? 用想当然的人话来说就是 把一堆无序的元素让它变得有序,一般我们认为的有序为递增或者递减。
先来介绍下几个排序算法,插入排序,交换排序,选择排序,二路归并排序,基数排序。这是前人的成果,那么即然有多种方法,必然会出现哪个比较牛逼,各自有啥优缺点。
我们会自然想到心目中最理想的排序算法,耗时最少,容易编写,无辅助空间,非常稳定,使用范围广。但是没有一个算法具有以上全部特点。所以评价一个排序算法的性能比较重要。
通常评价算法的标准:时间,额外存储空间,稳定性
二、插入排序
基本思路:每步将一个 待排序的元素 按其排序码的大小插入到已经排好序的一组元素的适当位置上去,直到元素全部插入为止。
根据不同的方法查找 插入位置,对应不同的插入排序算法。
(举个实例:军训排队,之前已经排好了一个纵队,此时有人要加入,于是教官让 新来的寻找自己的位置并插入)
1.直接插入排序
基本思路:把数组a[n]中待排的n个元素看成一个有序表和一个无序表,开始时有序表只包含第一个元素a[0],
无序表包含n-1个元素a[1]~a[n-1],排序过程中每次从无序表表中退出第一个元素,
把它插入到有序表的适当位置,使之成为新的有序表,
这样经过n-1次后无序表为空,有序表包含了全部元素,至此排序完毕。
先来看一个实例:
原始序列{49,38,65,97,76,13,27}
(1)只看a[0]49,一个数当然有序
{49} {38,65,97,76,13,27}
(2)插入38。因为38<49,所以49向后移动一个位置,38插入到原来49的位置,此趟排序的结果为
{38,49} {65,97,76,13,27}
(3)插入65。因为65>49。所以49无需移动,65放在49之后,此趟排序的结果为
{38,49,65} {97,76,13,27}
(5)插入97。因为97>65,所以65无需移动,97放在65之后,此趟排序的结果为
{38,49,65,97} {76,13,27}
(6)插入76。因为76<97,所以97向后移动一个位置,继续比较76>65,65无需移动,76放在65和97之间,此趟排序的结果为
{38,49,65,76,97} {13,27}
(7)插入13。因为13<97,所以97向后移动一个位置;13<76,所以76向后移动一个位置;13<65,所以65向后移动一个位置;
13<49,所以49向后移动一个位置;13<38,所以49向后移动一个位置;此时前面没有数了,所以载第一个位置插入13。
此趟排序的结果为 {13,38,49,65,76,97} {27}
(4)插入27。因为27<97,所以97向后移动一个位置;依次比较.....确定13应插入在13和28之间,
此趟排序的结果为 {13,27,38,49,65,76,97} {}
此时排序完毕
#include<stdio.h>
#include<stdlib.h>
void insertSort(int arr[],int n){
int i,j,temp;
for(i=1;i<n;++i)
{
temp=arr[i];//将待插入关键字暂存入temp
j=i-1;
//下面的循环完成从 待排关键字 之前的关键字 开始扫描,如果大于待排关键字,则后移一位
while(j>=0&&temp<arr[j]){
arr[j+1]=arr[j];
--j;
}
arr[j+1]=temp;//找到插入位置,将temp种暂存的待排关键字插入
}
}
int main(){
int array[]={4,2,1,4,2,8,3,1,10};
int i;
int len=sizeof(array)/sizeof(array[0]);
printf(" \n排序前\n");
for( i=0;i<len;i++)
printf("%d,",array[i]);
printf("\n");
insertSort(array,len);
printf(" \n排序后\n");
for( i=0;i<len;i++)
printf("%d,",array[i]);
printf("\n");
return 0;
}
性能分析:
空间:使用常数个辅助单元,空间复杂度为O(1)
时间:最好情况元素全部有序,每插入一个元素,只需比较一次不用移动元素,时间复杂度O(n)
最坏情况全部逆序,总比较次数 ,总移动次数为
平均情况下时间复杂度O()
稳定性:由于不设一形同元素相对位置发生变化,所以稳定
//基于静态链表的直接插入
#include "staticList.h"
int insertSort(staticLinkedList& L){
//对L.elem[1]...L.elem[n]按其排序码key排序,这个表是一个静态链表
//L.elme[0]作为排序后各个元素所构成的有序循环链表的头结点使用
int i,p,pre;
L.elem[0].key=maxValue;
L.elem[0].linnk=1;
L.elem[i].link=0;
for(int i=2;i<=L.n;i++)
p=L.elem[0].link; //p是扫描指针
pre=0; //pre指向p的前驱
while(L.elem[p].key<=L.elem[i].key) //沿着链寻找插入位置
{
pre=p; //pre跟上,p循环检测下一个结点
p=L.elem[p].link;
}
L.elem[i].link=p; //结点i链入pre与p之间
L.elem[pre].link=i;
}
2.折半插入排序
基本思路:设在表中有一个元素序列a[0],a[1].........a[n-1]。其中a[0],a[1].........a[i-1]已经排好序(0<i<n)。
在插入a[i]时,利用折半查找法寻找在有序表中a[0],a[1].........a[i-1]内寻找a[i]的插入位置。
在插入到已排序的数据时采用来折半查找(二分查找),取已经排好序的数组的中间元素,
与插入的数据进行比较,
如果比插入的数据大,那么插入的数据肯定属于前半部分,否则属于后半部分,
依次不断缩小范围,确定要插入的位置。
执行流程:
(作者:Swen_9826链接:https://www.jianshu.com/p/68de9fea390e)
原始有序序列{5,2,6,0,9}
-
初始状态:设5为有序,其中i为1,即:5 2 0 6 9
-
第一趟排序:low为0,high为0,则中间值下标为0((low+high)/2,下文都是如此计算),即5大于2,则插入到5前面,然后i自增。即:2 5 6 0 9
-
第二趟排序:low为0,high为1,则中间值下标为0,即2小于6, 然后low等于中间值的下标加1,继续找中间值为5小于6,则6插入到5后面,然后i自增。 即:2 5 6 0 9
-
第三趟排序:low为0,high为2,则中间值下标为1,即5大于0然后high等于中间值得下标减1,继续找中间值为2大于0,则插入到2前面,然后i自增。 即:0 2 5 6 9
-
第四趟排序:low为0,high为3,则中间值下标为1,即2小于9,然后low等于中间的得下标加上1,继续找中间值为5小于9,然后low等于中间值得下标加上1,继续找中间值为6小于9,则插入到6后面,然后i自增。 即:0 2 5 6 9
-
最终的答案为:0 2 5 6 9
//来自https://www.jianshu.com/p/68de9fea390e
public class BinaryInsertSort {
public static void main(String[] args){
int arr[] = { 5 , 2 , 6 , 0 , 9 };
//打印排序前的数据
System.out.println("排序前的数据:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
binaryInsertSort(arr);
//打印排序后的数据
System.out.println();
System.out.println("排序后的数据:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
private static void binaryInsertSort(int arr[]){
int low,high,m,temp,i,j;
for(i = 1;i<arr.length;i++){
//折半查找应该插入的位置
low = 0;
high = i-1;
while(low <= high){
m = (low+high)/2;
if(arr[m] > arr[i])
high = m - 1;
else
low = m + 1;
}
//统一移动元素,然后将这个元素插入到正确的位置
temp = arr[i];
for(j=i;j>high+1;j--){
arr[j] = arr[j-1];
}
arr[high+1] = temp;
}
}
}
#include<stdio.h>
#include<stdlib.h>
void BinSort(int arr[],int n){
int i,j;
int low,high,mid;
for(i=2;i<=n;i++){
arr[0]=arr[i];
low=1; high=i-1;
while (low<=high){
mid=(low+high)/2;
if(arr[0]<arr[mid])
high=mid-1;
else
low=mid+1;
}
for(j=i-1;j>=low;j--){
arr[i]=arr[j];
i--;
}
arr[low]=arr[0];
}
}
void put(int arr[],int n){
int j;
for(j=1;j<n;j++)
printf("%d\t",arr[j]);
printf("\n");
}
int main(){
int array[]={0,4,2,1,4,2,8,3,1,10};
//这里为了方便,所以数组第一个放了0,实际要排序的数为4,2,1,4,2,8,3,1,10
int len=sizeof(array)/sizeof(array[0]);
printf(" \n排序前\n");
put(array,len);
BinSort(array,len);
printf(" \n排序后\n");
put(array,len);
}
性能分析:最好情况 ,最差
,平均
, 空间复杂度O(1)
3.希尔排序
(又叫缩小增量排序)
设计思路:设待排元素序列有n个,首先选取一个整数gap<n作为间隔,将全部元素分为gap个子序列。然后缩小间隔gap,例如gap=,重复上述的子序列划分和排序工作。直到最后gap==1,将所有元素放在同一个序列中排序为止。
人话:用增量来将数组进行分隔,直到增量为1。底层还是插入排序
简记:整体增量分割,各部直接插入。
这规则就对应增量的选取,通常这个增量是不断缩小的。
举例:
原始序列49,38,65,97,76,13,27,49*,55,04 (因为有两个49,用下*加以区分)
(1)以增量5分割序列,得到如下子序列
序列1 | 49 | 13 | ||||||||
序列2 | 38 | 27 | ||||||||
序列3 | 65 | 49* | ||||||||
序列4 | 97 | 55 | ||||||||
序列5 | 76 | 04 |
分别对这5个子序列进行直接插入排序,得到:
序列1 | 13 | 49 | ||||||||
序列2 | 27 | 38 | ||||||||
序列3 | 49* | 65 | ||||||||
序列4 | 55 | 97 | ||||||||
序列5 | 04 | 76 |
一趟希尔排序结果为13 ,27 , 49* ,55, 04, 49 , 38 , 65, 97, 76
(2)在对上面排序的结果以3为增分割,得到
序列1 | 13 | 55 | 38 | 76 | ||||||
序列2 | 27 | 04 | 65 | |||||||
序列3 | 49* | 49 | 97 |
分别对子序列进行直接插入排序
序列1 | 13 | 38 | 55 | 76 | ||||||
序列2 | 04 | 27 | 65 | |||||||
序列3 | 49* | 49 | 97 |
第二趟希尔排序为
13,04,49*,38,27,49,55,65,97,76
(3)以增量1分割,即对上面结果的全体关键字进行一趟直接插入排序,从而完成整个希尔排序。
最终希尔排序,04,13,27,38,49*,49,55,65,76,97
性能分析: 空间复杂度 O(1)
时间复杂度一般为 ,最坏为
(注:关于shell排序的时间复杂度并没有严格的结论)
稳定性由于相同关键字相对次序可能被改变,所以不稳定
#include<stdio.h>
#include<stdlib.h>
void swap(int *a,int *b)
{
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
void shellsort(int array[],int len)
{
int gap,i,j;
for(gap=len/2;gap>0;gap/=2)
{
for(i=gap;i<len;i++)
{
for(j=i-gap;array[j+gap]<array[j]&&j>=0;j-=gap)
{
swap(&array[j+gap],&array[j]);
}
}
}
}
void put(int arr[],int n){
int j;
for(j=0;j<n;j++)
printf("%d\t",arr[j]);
printf("\n");
}
void main(){
int array[]={0,4,2,1,4,2,8,3,1,10};
int i;
int len=sizeof(array)/sizeof(array[0]);
printf(" \n排序前\n");
put(array,len);
shellsort(array,len);
printf(" \n排序后\n");
put(array,len);
}
void ShellSort(Elemtype A[],int n){
//1.前后记录位置的是dk,不是1
//2.A[0]只是暂存单元,不是哨兵,当j<=0,找到插入位置
for(dk=n/2;dk>=1;dk=dk/2) //步长变化
for(i=dk+1;i<=n;++i)
if(A[i].key<A[i-dk].key){ //需将A[i]插入有序增量子表
A[0]=A[i]; //暂存在A[0]
for(j=i-dk;j>0&&A[0].key<A[j].key;j-=dk)
A[j+dk]=A[j]; //记录后移,查找插入的位置
A[j+dk]=A[0]; //插入
}
}