排序(插入)

 

// 尽量让文章通俗易懂//

排序生活中常常会用到,公司业级考核排名,学校成绩排名等等。

假如我们是一个小白,面对几百个杂乱的数字 如何让他变得有序呢?

我们会想到一个一个排序呗,先找到一个放在第一位,碰到比他大的放在 后面,比他小的放在前面。

或者在这些树中找出最小的放在第一个,接着找次小的,依次..................

这或许是解决问题的方法,但还有更高效的办法吗?

目录

  一、基本 概念

 二、插入排序

1.直接插入排序

2.折半插入排序

3.希尔排序


_______________________________________________________________________________________________

    一、基本 概念

        那什么是排序呢?  用想当然的人话来说就是  把一堆无序的元素让它变得有序,一般我们认为的有序为递增或者递减。

        先来介绍下几个排序算法,插入排序,交换排序,选择排序,二路归并排序,基数排序。这是前人的成果,那么即然有多种方法,必然会出现哪个比较牛逼,各自有啥优缺点。

        我们会自然想到心目中最理想的排序算法,耗时最少,容易编写,无辅助空间,非常稳定,使用范围广。但是没有一个算法具有以上全部特点。所以评价一个排序算法的性能比较重要。

          通常评价算法的标准:时间,额外存储空间稳定

二、插入排序

          基本思路:每步将一个 待排序的元素  按其排序码的大小插入到已经排好序的一组元素的适当位置上去,直到元素全部插入为止。

            根据不同的方法查找  插入位置,对应不同的插入排序算法。

             (举个实例:军训排队,之前已经排好了一个纵队,此时有人要加入,于是教官让 新来的寻找自己的位置并插入)

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)

                     最坏情况全部逆序,总比较次数\sum_{i=2}^{n}i ,总移动次数为\sum_{i=2}^{n}i+1

                     平均情况下时间复杂度O(n^2

           稳定性:由于不设一形同元素相对位置发生变化,所以稳定

//基于静态链表的直接插入
#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(nlog_{2} n)  ,最差O(n^2),平均O(n^2),                空间复杂度O(1)

3.希尔排序

    (又叫缩小增量排序)

       设计思路:设待排元素序列有n个,首先选取一个整数gap<n作为间隔,将全部元素分为gap个子序列。然后缩小间隔gap,例如gap=\left \lceil gap/2 \right \rceil,重复上述的子序列划分和排序工作。直到最后gap==1,将所有元素放在同一个序列中排序为止。

        人话:用增量来将数组进行分隔,直到增量为1。底层还是插入排序

        简记:整体增量分割,各部直接插入。

       这规则就对应增量的选取通常这个增量是不断缩小的。

举例:

 原始序列49,38,65,97,76,13,27,49*,55,04  (因为有两个49,用下*加以区分)

(1)以增量5分割序列,得到如下子序列

序列149    13    
序列2 38    27   
序列3  65    49*  
序列4   97    55 
序列5    76    04

分别对这5个子序列进行直接插入排序,得到:

序列113    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为增分割,得到

序列113  55  38  76
序列2 27  04  65  
序列3  49*  49  97 

分别对子序列进行直接插入排序

序列113  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)

                     时间复杂度一般为O(n^{1.5}) ,最坏为O(n^{2})    (注:关于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];   //插入
        }
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值