• 插入排序:二路插入


            在上一篇博客中:插入排序:直接插入、交换插入、折半插入。提到了三种插入排序的详细实现。

    只是仍有改进的地方。比如序列 2 1 3。当把1往前插入时。由于1<2,则应当把1插入到2的前面。

    在上述三种插入排序方法的实现中,都是把1、2位置交换。于是,我们想有没有可能不进行交换,由于交换总是相当耗时的。

    可是1必需要排到2的前面。可2的前面没有位置了啊?嗯,初看是这种。试想这是一个循环的数组呢?这就是二路插入最核心的想法。

    思路:

    1. 构建一同样大小的循环数组b,把原数组的元素依次插入,最后按合适次序赋值回原数组。

      怎样实现循环呢?有办法的。可參考约瑟夫问题的数组解法中是怎样实现的。

    2. 把原数组的第一个值a[0]复制过去。b[0]=a[0],作为循环数组的第一个数。

      当然,也可选择其他的数作为第一个数。

    3. 若a[i]<b[first]。则变化first:first=(first-1+n)%n。b[first]=a[i]
    4. 若a[i]>=b[last],则变化last:last++(注意这里不是必需这样写:last=(last+1)%n),b[last]=a[i]
    5. 若b[first]<=a[i]<b[last]。则选择适当的策略,插入下图中的一路位置。
    6. 这里的二路是什么意思?没有看到哪里解释过,我的理解是,看下图:

    上图中,first指向已拍好序列的第一个,last指向已排好序列的最后一个。

    假设按从小到大排序,first指向最小,last指向最大的。假设某一个数据a,且b[first]<=a<b[last],则a应插入图中一路所看到的的位置,其他的应插入二路。

    也就是说,能够插入的位置总的分为两路-二路插入。

    显然,一路位置的元素是有序的。

    那么在往一路插入时,可直接插入。也可二分插入。先看下直接插入时的代码:

    代码一:
    <span style="font-size:25px;">
    void InsertSort1(int a[], int n)    //二路插入
    {
    	int first, last;
    	first = last = 0;
    	int *b = new int[n];
    	b[0] = a[0];
    	for (int i = 1; i < n; i++)
    	{
    		if (a[i] < b[first])
    		{
    			first = (first - 1 + n) % n;   //first的变化必须这样写
    			b[first] = a[i];
    		}
    		else if (a[i] >= b[last])
    		{
    			last++;     //有的人这样写:last=(last+1)%n,事实上不是必需,last是不会超过n-1的。
    			b[last] = a[i];
    		}
    		else
    		{
    			int k;
    			for (k = last+1; a[i] < b[(k-1+n)%n]; k=(k-1+n)%n)     // 使用直接插入
    				b[k] = b[(k - 1 + n)%n];
    			b[k] = a[i];
    			last++;
    		}
    	}
    	for (int i = 0; i < n; i++)
    		a[i] = b[(i + first) % n];
    	delete[]b;
    }
    </span>

    显然,在对一路二分插入时,更高效,代码例如以下:
    代码二:在二分查找时,我们选择左闭右开的区间。若是无法理解折半查找的过程,强烈推荐看下:插入排序:直接插入。交换插入。折半插入
    <span style="font-size:25px;">
    void InsertSort2(int a[], int n)    //二路插入
    {
    	int first, last;
    	first = last = 0;
    	int *b = new int[n];
    	b[0] = a[0];
    	for (int i = 1; i < n; i++)
    	{
    		if (a[i] < b[first])
    		{
    			first = (first - 1 + n) % n;
    			b[first] = a[i];
    		}
    		else if (a[i]>=b[last])
    		{
    			last++;
    			b[last] = a[i];
    		}
    		else
    		{
    			int low, high, mid, d;
    			low = first, high = last;
    			while (low != high)      //折半查找
    			{
    				d = (high-low+n) % n;    //元素个数
    				mid = (low + d / 2) % n;    //中间位置
    				if (a[i] < b[mid])
    					high = mid;
    				else
    					low = (mid + 1) % n;
    			}
    			for (int k = last + 1; k != low; k = (k - 1 + n) % n)  //移动元素
    				b[k] = b[(k - 1 + n) % n];
    			b[low] = a[i];
    			last++;
    		}
    	}
    	for (int i = 0; i < n; i++)
    		a[i] = b[(i + first) % n];
    	delete[]b;
    }
    </span>

    update: 2014-6-2 0:11
    以下给出一測试代码,具体观察数组b的变化情况,
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    void printArray(int a[], int n)   //打印数组 
    {
    	for (int i = 0; i<n; i++)
    		printf("%-4d", a[i]);
    	printf("
    ");
    }
    void InsertSort2(int a[], int n)    //二路插入
    {
    	int first, last;
    	first = last = 0;
    	int *b = new int[n];
    	memset(b,0,n*sizeof(int));   //数组b的内存空间清零
    	b[0] = a[0];
    	printf("数组下标:
    ");
    	for (int i = 0; i < n; i++)
    		printf("%-4d",i);
    	printf("
    ");
    	printf("数组b的变化
    ");
    	for (int i = 1; i < n; i++)
    	{
    		printArray(b, n);
    		if (a[i] < b[first])
    		{
    			first = (first - 1 + n) % n;
    			b[first] = a[i];
    		}
    		else if (a[i]>=b[last])
    		{
    			last++;
    			b[last] = a[i];
    		}
    		else
    		{
    			int low, high, mid, d;
    			low = first, high = last;
    			while (low != high)      //折半查找
    			{
    				d = (high-low+n) % n;    //元素个数
    				mid = (low + d / 2) % n;    //中间位置
    				if (a[i] < b[mid])
    					high = mid;
    				else
    					low = (mid + 1) % n;
    			}
    			for (int k = last + 1; k != low; k = (k - 1 + n) % n)
    				b[k] = b[(k - 1 + n) % n];
    			b[low] = a[i];
    			last++;
    		}
    	}
    	printArray(b,n);
    	for (int i = 0; i < n; i++)
    		a[i] = b[(i + first) % n];
    	delete[]b;
    }
    int main()
    {
    	const int N = 6;
    	int a[N];
    	srand((unsigned)time(NULL));
    	printf("原数组a
    ");
    	for (int i = 0; i < N; i++)
    	{
    		a[i] = rand() % 100;
    		printf("%-4d",a[i]);
    	}
    	printf("
    ");
    	InsertSort2(a, N);
    	printf("经排序后的数组a
    ");
    	printArray(a, N);
    	printf("
    ");
    	system("pause");
    	return 0;
    }
    某一次的执行结果是这种:


    小结:
    1. 这里我们使用的区间是左闭右开的,这是为了方便后面循环的终止。

    2. 关于first和last的移动,大家画下图,非常easy明确。从上面的执行结果能够看出,last是从0開始递增的,且不会超过n-1,这一点是显然的。
    3. 在往二路插入时。是不须要移动元素的,这就是二路插入相对于前三种改进的地方。

    4. 若a[0]即是最小或最大的元素,则退化为直接插入,此时无法降低移动次数。
    5. 在代码二中,关于元素个数d=(high-low+n)%n,要注意:因为这里选取的是左闭右开的区间[low,high),好比区间[1,2)中整数个数是2-1=1,但在区间[1,2]中整数个数是2-1+1=2。所以这里是high-low,后面的+n,你懂的。

      若是写成d=(high-low+1+n)%n,会进入死循环。你能够试一下。



    代码就是折腾,多折腾才有进步!


    若是有所帮助,顶一个哦!



    全部内容的文件夹

  • 相关阅读:
    《Android进阶之光》--RxJava
    《Android进阶之光》--RxJava实现RxBus
    《Android进阶之光》--RxJava结合Retrofit访问网络
    《Java并发编程的艺术》--Java中的锁
    《深入探索Androdi热修复技术原理(阿里巴巴)》--读书笔记
    Smali语法
    《Android进阶之光》--网络编程与网络框架
    《Android进阶之光》--多线程编程
    《Android进阶之光》--View体系与自定义View
    《Android进阶之光》--Material Design
  • 原文地址:https://www.cnblogs.com/blfshiye/p/5181980.html
Copyright © 2020-2023  润新知