精华内容
下载资源
问答
  • 数据结构排序算法学习之插入排序

    万次阅读 2019-09-02 08:46:43
    数据结构排序算法之插入排序<一> 插入排序(Insertion sort) 是一种简单直观且稳定的排序算法。如果有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,...

    数据结构排序算法之插入排序<一>

    插入排序(Insertion sort)

    是一种简单直观且稳定的排序算法。如果有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外(让数组多一个空间才有插入的位置),而第二部分就只包含这一个元素(即待插入元素)。在第一部分排序完成后,再将这个最后元素插入到已排好序的第一部分中。

    插入排序的基本思想

    每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。

    分类
    emmmm这里只说三种主要的:

    1. 直接插入排序
    2. 折半插入排序(二分插入排序)
    3. 希尔排序(又称缩小增量排序)

    1.直接插入排序

    把n个待排元素分为有序区和无序区,开始时有序区中只包含1个元素,无序区中则包含n-1个元素,排序过程中每次都从无序区中取出第一个元素,将它插入到有序区中的适当位置,使之成为一个有序的表,重复n-1次可完成排序过程。

    Java实现:

    package priv.qcy.sort.insert;
    
    public class InsertSort {
    
    	public static void insertSort(int[] a, int n) {
    
    		int i, j;
    		for (i = 1; i < n; i++) {
    			int temp = a[i];
    			for (j = i - 1; j >= 0 && a[j] > temp; j--) {
    				a[j + 1] = a[j];
    
    			}
    			a[j + 1] = temp;
    
    		}
    
    	}
    
    	public static void main(String[] args) {
    		int[] a = { 20, 40, 30, 10, 60, 50 };
    		System.out.print("排序前:");
    		for (int i = 0; i < a.length; i++) {
    			System.out.print(a[i] + "  ");
    
    		}
    		System.out.println();
    		insertSort(a, a.length);
    		System.out.print("排序后:");
    		for (int i = 0; i < a.length; i++) {
    			System.out.print(a[i] + "  ");
    
    		}
    
    	}
    
    }
    
    

    直接插入排序时间复杂度
    直接插入排序的时间复杂度是O(N^2)。
    假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),而直接插入排序需要遍历N-1次。因此,直接插入排序的时间复杂度是O(N^2)。

    直接插入排序稳定性
    直接插入排序是稳定的算法,它满足稳定算法的定义。
    算法稳定性 – 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!

    特点

    1. 也适用于链式存储结构
    2. 更适用于初始记录基本有序的情况(当N较大且无序时,此算法时间复杂度较高)
    展开全文
  • 数据结构排序算法学习之插入排序3

    万次阅读 2019-09-03 20:28:31
    数据结构排序算法之插入排序<三> 3.希尔排序(递减增量排序) 希尔排序从“减少记录个数”和“序列基本有序”两个方面对直接插入排序算法进行改进,但插入排序一般来说是低效的,因为插入排序每次只能将数据...

    数据结构排序算法之插入排序<三>

    3.希尔排序(递减增量排序)

    希尔排序从“减少记录个数”和“序列基本有序”两个方面对直接插入排序算法进行改进,但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

    基本思想:

    先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序。
    对于n个待排序的数列,取一个小于n的整数gap(gap被称为步长)将待排序元素分成若干个组子序列,所有距离为gap的倍数的记录放在同一个组中;然后,对各组内的元素进行直接插入排序。 这一趟排序完成之后,每一个组的元素都是有序的。然后减小gap的值,并重复执行上述的分组和排序。重复这样的操作,当gap=1时,整个数列就是有序的。

    希尔排序

    Java实现:

    package priv.qcy.sort.insert;
    
    public class ShellSort {
    
    	public static void shellSort(int[] a, int n) {
    		for (int gap = n / 2; gap > 0; gap /= 2) {
    			for (int i = 0; i < gap; i++) {
    				for (int j = i + gap; j < n; j += gap) {
    
    					if (a[j] < a[j - gap]) {
    						int temp = a[j];
    						int k = j - gap;
    						while (k >= 0 && a[k] > temp) {
    
    							a[k + gap] = a[k];
    							k -= gap;
    						}
    						a[k + gap] = temp;
    					}
    
    				}
    			}
    
    		}
    
    	}
    
    	public static void main(String[] args) {
    		int[] a = { 9, 1, 2, 5, 7, 4, 8, 6, 3, 5 };
    		System.out.print("排序前:");
    		for (int i = 0; i < a.length; i++) {
    			System.out.print(a[i] + "  ");
    		}
    		System.out.println();
    		shellSort(a, a.length);
    		System.out.print("排序后:");
    		for (int i = 0; i < a.length; i++) {
    			System.out.print(a[i] + "  ");
    
    		}
    
    	}
    
    }
    
    

    特点:

    • 是插入排序的一种更高效的改进版本,但跳跃导致希尔排序是非稳定排序算法。
    • 只适用于顺序结构
    • 适用于初始记录无序,n较大时的情况

    希尔排序时间复杂度
    希尔排序的时间复杂度与增量(即,步长gap)的选取有关。例如,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为O(N²),而Hibbard增量的希尔排序的时间复杂度为O(N3/2)。

    展开全文
  • 数据结构排序算法学习之插入排序2

    万次阅读 2019-09-03 19:56:15
    数据结构排序算法之插入排序<二> 2.折半插入排序 基本思想: Java实现: package priv.qcy.sort.insert; public class BinaryInsertSort { public static void binaryInsertSort(int[] a) { int n = a....

    数据结构排序算法之插入排序<二>

    2.折半插入排序

    基本思想:

    折半插入算法是对直接插入排序算法的改进,排序原理同直接插入排序

    例子:int[] arr={5,2,6,0,9};经行折半插入排序

    在这里插入图片描述

    Java实现:

    package priv.qcy.sort.insert;
    
    public class BinaryInsertSort {
    
    	public static void binaryInsertSort(int[] a) {
    		int n = a.length;
    		int i, j;
    		for (i = 1; i < n; i++) {
    			int temp = a[i];
    			int low = 0;
    			int high = i - 1;
    			while (low <= high) {
    				int mid = (low + high) / 2;
    				if (a[mid] > temp) {
    					high = mid - 1;
    
    				} else {
    					low = mid + 1;
    				}
    
    			}
    			for (j = i - 1; j >= low; j--) {
    				a[j + 1] = a[j];
    			}
    			a[low] = temp;
    
    		}
    
    	}
    
    	public static void main(String[] args) {
    		int[] a = { 20, 40, 30, 10, 60, 50 };
    		System.out.print("排序前:");
    		for (int i = 0; i < a.length; i++) {
    			System.out.print(a[i] + "  ");
    
    		}
    		System.out.println();
    		binaryInsertSort(a);
    		System.out.print("排序后:");
    		for (int i = 0; i < a.length; i++) {
    			System.out.print(a[i] + "  ");
    
    		}
    	}
    }
    
    

    时间复杂度:可以看出,折半插入排序减少了比较元素的次数,约为O(nlogn),比较的次数取决于表的元素个数n。因此,折半插入排序的时间复杂度仍然为O(n²),但它的效果还是比直接插入排序要好。

    空间复杂度:排序只需要一个位置来暂存元素,因此空间复杂度为O(1)。

    特点:

    • 只适用于顺序结构
    • 适合初始记录无序,n较大的情况
    • 稳定,相对于直接插入排序元素减少了比较次数
    展开全文
  • 数据结构排序算法大总结(C语言),总结整理直接插入排序、折半插入排序、希尔排序、冒泡排序、快速排序,简单选择排序、堆排序、归并排序、基数排序九大排序算法以及外排序的算法思想,结合图片演示原理,含源码。

    最近复习数据结构,看完排序,就想着赶紧总结一下,供自己整理学习,也供大家一起学习进步。

        目    录

    一、概述

    二、内排序

    1.插入排序

    1)直接插入排序

             2)折半插入排序(先折半查找再移动插入)

             3)希尔排序(缩小增量排序)

    2.交换排序

    1)冒泡排序

    2)快速排序

    3.选择排序

    1)简单选择排序

    2)堆排序

    4.归并排序

    5.基数排序(桶排序)

    1)LSD

    2)MSD

    三、内部排序总结

    四、外部排序

    1.置换选择排序

    2.最佳归并树

    3.败者树

     


    一、概述

    1. 排序分为内排序和外排序。衡量算法的三个主要标准,空间复杂度、时间复杂度、稳定性(稳定性指关键字相同的元素,在排序后前后相对顺序不变,本身不含褒贬)。
    2. 内排序又分为插入排序、交换排序、选择排序、归并排序、基数排序(根据算法实现的思想不同)。
    3. 外排序主要采用归并排序,为提高外部排序的效率有三种相关的优化算法。

    二、内排序

    1.插入排序

    插入排序包含:直接插入排序、折半插入排序、希尔排序

    1)直接插入排序

    算法思想:将数据集合看作是两个部分,前部分是有序集合,后半部分是待排序集合,每次从待排序的集合中取第一个元素,将其插入到有序集合应该放入的位置。在插入时候涉及到向后移位的操作,因为待插入位置之后的元素要向后移动,在直接插入排序中,比较移位同时进行的,待插入元素依次向前比较,若前一个元素大于它,则将前一个元素向后移动,再向前比较下一个元素,如图此时待排序元素为2,2与4比较,2小于4,在4前面,4向后移,2比较3,3向后移,2比较1,2在1后,则插入1之后。

    void InsertSort(int array[],int len){
    	int i,j,key;
    	for(i=1;i<len;i++){
    		if(array[i]<array[i-1]){
    			key=array[i];
    			for(j=i-1;j>=0&&array[j]>key;j--){
    				array[j+1]=array[j];
    			}
    			array[j+1]=key;
    		}
    		
    	}
    }

    空间复杂度:O(1);

    时间复杂度:最好情况,序列已经是升序排列了,需要进行的比较操作需(n-1)次即可。最坏情况,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2);

    稳定性:稳定;

    使用范围:适用于顺序存储和链式存储。

    总结:排序效率与初始序列的状态有关,适用于初始基本有序且规模不太大的序列,对于初始状态基本无序的序列,时间消耗较大。

    2)折半插入排序(先折半查找再移动插入)

    算法思想:直接插入排序是边比较边移动(如递增序列,若小于前一个数,前一个数向后移,继续向前比较),而折半插入排序则是将比较移动进行分离。先折半查找元素待插入的位置,再统一移动待插入位置之后的所有元素。因此,折半插入排序主要是减少了元素比较的次数,但移动次数没变。

    void BinaryInsertSort(int array[],int len){
    	int i,j,low,high,mid,key;
    	for(i=1;i<len;i++){
    		if(array[i]<array[i-1]){
    			key=array[i];
    			high=i-1;
    			low=0;
    			while(low<=high){
    				mid=(high+low)/2;
    				if(array[mid]>array[i])
    					high=mid-1;
    				else
    					low=mid+1;
    			}
    			for(j=i;j>low;j--){
    				array[j]=array[j-1];
    			}
    			array[low]=key;
    		}
    	}
    }

    空间复杂度:O(1);

    时间复杂度:比较次数减小,平均为O(nlog2n),移动次数未减少,平均来说,算法的时间复杂度为O(n^2);

    稳定性:稳定;

    使用范围:适用于顺序存储。

    总结:比较次数与待排序列初始状态无关,移动次数与初始序列状态有关,对于顺序存储的数据,使用折半插入可以节省比较的时间,但总的移动次数没有变。

    3)希尔排序(缩小增量排序)

    直接插入排序适用于数据量不大的排序,希尔排序适用于数据量比较大的情况。

    算法思想:将数据分成若干组,假设a[10]内存有10个无序元素,取增量d为10/2=5,则将10个数据分为5组,每组2个元素,将下标为0,5(0+d)定义为1组,1,6(1+d)定义为2组,3,7(3+d)定义为3组,4,8(4+d)定义为4组,5,9(5+d)定义为5组(注意这里是说的下标),然后每组中的元素进行比较,如不符合排序规则就进行插入排序操作。

    假设a[10]={8,5,3,1,2,4,6,9,7,0},递增排序;

    第一轮增量为5,分组如下:

    8,4为一组,4在8前,插入排序,4插入8前,则相当于两数互换,4,8;

    5,6为一组,5在6前,插入排序,位置不变;

    3,9为一组,3在9前,插入排序,位置不变;

    1,7为一组,1在7前,插入排序,位置不变;

    2,0为一组,0在2前,插入排序,0插入2前,相当于两数互换,0,2;

    第二轮,取增量为d=5/2=2(此时5为第一次取的增量),分成2组,每组5个元素,下标0,2(0+d),4(0+2d),6(0+3d),8(0+4d)为第一组,下标1,3(1+d),5(1+2d),7(1+3d),9(1+4d)为第二组,分组情况如图。

    此时执行插入排序,即{4,3,0,6,7}和{5,1,8,9,2}分别执行直接插入排序,直接插入排序过程就不赘述了(依次向前比较),比较结果插入相应位置,结果为{0,3,4,6,7}和{1,2,5,8,9},此时排序情况如图。

    第三轮,取增量为d=2/2=1(2为第二轮所取的增量),分成1组,即全部10个元素执行直接插入排序,此时,因为元素经过上面两轮的排序,已经具有较好的局部有序性,则很快就能得到最终结果,直接插入排序不做赘述。

    void ShellSort(int a[],int len){
    	int i,j,d=len/2,key;
    	while(d>=1){
    		for(i=d;i<len;i++){
    			if(a[i]<a[i-d]){
    				key=a[i];
    				for(j=i-d;j>=0&&a[j]>key;j=j-d)
    					a[j+d]=a[j];
    				a[j+d]=key;
    			}
    		}
    		d=d/2;
    	}
    }

    空间复杂度:O(1);

    时间复杂度:依赖于增量函数,最坏情况下,算法的时间复杂度为O(n^2);

    稳定性:不稳定;

    使用范围:适用于顺序存储。

    总结:中等大小规模表现良好,对规模非常大的数据排序不是很好的选择。但是比O(n^2)复杂度的算法快得多。

    2.交换排序

    交换排序包含:冒泡排序,快速排序

    交换,就是指两个元素互换位置,通过两个元素的比较,将这两个元素通过交换按照正确的顺序排列,不管是冒泡排序还是快速排序,每一趟排序都会将一个元素放置到有序序列的最终位置

    1)冒泡排序

    算法思想:将待排序表中的元素从后往前(或从前往后)两两比较相邻元素的值,若为逆序则交换它们的位置,直到序列比较完,这样最小的元素(假设递增排序)就会被交换到第一个位置,就这称之为一趟冒泡,每一趟冒泡都会将待排序元素中最小的一个放到有序序列的最后。

    void BubbleSort(int a[],int len){
    	int i,j;
    	int flag=1;//初始设置,假设有序
    	for(i=0;i<len;i++){
    		for(j=len-1;j>i;j--)
    			if(a[j-1]>a[j]){
    				swap(a,j,j-1);
    				flag=0;//每趟比较,若发生交换则设为无序。
    		}
    		if(flag)
    			return;
    	}
    }

    空间复杂度:O(1);

    时间复杂度:最好的情况下O(n),最坏情况下O(n^2),平均O(n^2);

    稳定性:稳定;

    使用范围:比较和移动只涉及相邻元素,适用于顺序存储,链式存储。

    总结:每一次排序都会有一个元素放在最终位置,执行效率与初始状态有关。

    2)快速排序

    算法思想:在待排序的元素序列中,选取一个关键字作为key,然后将剩下的元素分为两个子序列,一个子序列是关键字大于key的,另一个子序列是关键字小于key的,这样关键字为key的元素在最终有序序列的位置就确定了。然后采用递归操作,依次递归两个子序列,通常我们将每个序列的第一个元素的关键字作为key,这样对于相对有序的初始序列,快排的效率会大大降低,所以快排一般处理初始序列基本无序的序列,才能发挥最大优势。还可以用以下两种思路提高效率,一是在划分的子序列规模较小时,采用直接插入排序算法处理后续工作,二是,对于key的选取,可以采用不同的key的选取方法,尽量将序列划分两个长度差不多的子序列,避免极端情况。

    举例,a[]={3,5,4,0,2},按从小到大排序,第一次排序过程如下图。

    void QuickSort(int a[],int low,int high){
    	int key=a[low],low_2=low,high_2=high;//保存当前的上下界
    	if(low>high)//递归终止条件
    		return;
    	while(low<high){
    		while(low<high&&a[high]>key)
    			high--;
    		a[low]=a[high];
    		while(low<high&&a[low]<key)
    			low++;
    		a[high]=a[low];
    	}
    	a[low]=key;
    	QuickSort(a,low_2,low-1);
    	QuickSort(a,low+1,high_2);
    }

    空间复杂度:利用了一个工作栈,最好O(log2(n+1)),最坏O(n),平均O(log2n);

    时间复杂度:与划分的两个子序列是否对称有关,假设取第一个元素的关键字为key,当初始状态基本有序时,划分不对称,为最坏情况O(n^2),理想状态下O(nlog2n),平均运行时间与理想状态较接近;

    稳定性:不稳定;

    使用范围:适用于顺序存储。

    总结:每趟排序都会有一个元素放到最终位置,执行效率与划分方法有关,也即与key的取值有关,快排是所有内部排序算法中平均性能最佳的算法,适用于大量数据排序。

    3.选择排序

    选择排序包含:简单选择排序,堆排序

    选择排序,就是每次从待排序序列中找出最小(最大)值插入到有序序列的后面,选择的过程比较简单,也比较容易理解。

    1)简单选择排序

    算法思想:简单选择排序中,要从n个元素中找到一个关键字最小值或最大值则需要比较n-1次,设置一个min用来记录关键字最小的元素位置,初始设为第一个元素的下标,每次取一个元素和min位置的元素比较关键字大小,根据结果更新min,每一趟就会选出一个关键字最小的元素,共需要n-1趟,共需要比较1+2+3+...+(n-1)=n(n-1)/2次。

    void SelectSort(int a[],int len){
    	int i,j,min;
    	for(i=0;i<len;i++){
    		min=i;
    		for(j=i+1;j<len;j++){
    			if(a[j]<a[min])
    				min=j;
    		}
    		if(min!=i)
    			swap(a,min,i);
    	}
    }

    空间复杂度:O(1);

    时间复杂度:始终为O(n^2);

    稳定性:不稳定;

    使用范围:适用于顺序存储,链式存储。

    总结:与序列的初始状态无关,每趟选择可以确定一个元素的最终位置,时间复杂度固定不变。

    2)堆排序

    算法思想:利用树形结构进行选择排序,将a[1...n]视为一棵完全二叉树的顺序存储结构,双亲结点元素的关键字大于孩子结点关键字的称为大根堆,双亲结点元素的关键字小于孩子结点关键字的称为小根堆。初始状态下,需要先建堆,这个过程称之为堆化(heapify),建堆完成后,这棵树的根节点就是最大值(大根堆),直接选择根节点的元素即是待排序列的最大值,进行下次选择时,将根节点与最后一个叶子结点对换位置,然后删除最后一个叶子结点,再次heapify,以此类推。

    heapify:由上可知,堆排序的关键在于heapify,heapify的算法思想是,从第n/2⌋(向下取整)个结点 的位置(这个位置是最后一个非叶子结点)开始,以大根堆为例,判断⌊n/2⌋结点的关键字是否大于两个孩子结点的关键字:

    (1)若小于,则将⌊n/2⌋结点与其关键字较大的孩子结点互换位置,然后向下进行判断,以同样的方法继续判断以互换位置的孩子结点为根节点的子树是否满足大根堆(只有发生互换,即出现不满足大根堆时才继续向下heapify);

    (2)若大于,则向上判断,判断第n/2⌋-1个结点,直至判断调整至根结点,这里为了方便建堆结束。

    例,a[8]={1,2,3,4,5,6,7,8},建大根堆过程如下:

    1>8/2=4,第4个结点为a[4-1]=4,判断该结点是否满足大根堆定义,孩子节点a[7]=8>a[3],不满足大根堆,互换,然后判断a[7]结点,无孩子结点,满足大根堆。

    2>向上判断,4-1=3,第3个结点为a[3-1]=3,孩子结点a[5]=6,a[6]=7都比a[2]要大,不满足大根堆,取最大值互换,a[2]与a[6]互换,判断a[6]结点,无孩子结点,满足大根堆。

    3>向上判断,3-1=2,第2个结点为a[2-1]=2,孩子结点a[3]=8,a[4]=5都比a[1]要大,不满足大根堆,取最大值互换,a[1]与a[3]互换,判断a[3]结点,此时a[3]=2,孩子结点a[7]=4,不满足大根堆,互换,判断a[7]结点,无孩子结点,满足大根堆。

    4>向上判断,2-1=1,第1个结点为a[1-1]=2,孩子结点a[1]=8,a[2]=7都比a[0]要大,不满足大根堆,取最大值互换,a[0]与a[1]互换,判断a[1]结点,此时a[1]=1,孩子结点a[3]=4,a[4]=5,不满足大根堆,取最大值互换,判断a[4]结点,a[4]=1,无孩子结点,满足大根堆建堆完成。

    //堆化
    //heapify想要输出序列为递增,构造大根堆
    void Heapify(int tree[],int i,int n){
    	int c1=i*2+1;
    	int c2=i*2+2;
    	int max=i;
    	if(c1<n&&tree[c1]>tree[max]){
    		max=c1;
    	}
    	if(c2<n&&tree[c2]>tree[max]){
    		max=c2;
    	}
    	if(max!=i){
    		swap(tree,i,max);
    		Heapify(tree,max,n);
    	}
    }
    void BuildHeap(int tree[],int len){
    	int i;
    	//建大根堆
    	for(i=len/2;i>=0;i--)
    		Heapify(tree,i,len);
    	//排序
    	for(i=len-1;i>0;i--){
    		swap(tree,0,i);
    		Heapify(tree,0,i);
    	}
    }

    空间复杂度:O(1);

    时间复杂度:建堆时间O(n),n-1次调整,每次调整O(log2n),最坏情况O(nlog2n);

    稳定性:不稳定;

    使用范围:适用于顺序存储。

    总结:每趟排序确定一个元素的最终位置,适用于大量数据排序的情况,即使在最坏的情况下,时间复杂度仍为O(nlog2n)。

    4.归并排序

    算法思想:归并的含义就是将两个或两个以上的有序表组合成一个新的有序表。设待排序序列中含有n个元素,可将其视为n个有序的子序列,每个序列长度为1,在归并算法中,每次取m(m<n)个子序列进行归并,则称为m路归并。第一趟归并时,将n个子序列分成s=⌈n/m⌉ 个组,每个组内有m个子序列,最后一个组可以小于m,每个进行m路归并排序,形成新的s个子序列;然后进行第二趟归并,分成⌈s/m⌉个组,每组进行m路归并,如此重复,直到合并为1个有序序列。

    假设,a[]={8,5,2,3,4,6,7,1,9},由小到大,2路归并排序如下;

    void Merge(int a[],int b[],int low,int mid,int high){
    	int i,j,k=low;
    	for(i=low;i<=high;i++)//复制到b
    		b[i]=a[i];
    	for(i=low,j=mid+1;i<=mid&&j<=high;k++)
    		if(b[i]<=b[j])
    			a[k]=b[i++];
    		else
    			a[k]=b[j++];
    	while(i<=mid)
    		a[k++]=b[i++];
    	while(j<=high)
    		a[k++]=b[j++];
    }
    void MergeSort(int a[],int b[],int low,int high){
    	int mid;
    	if(low<high){
    		mid=(low+high)/2;
    		MergeSort(a,b,low,mid);
    		MergeSort(a,b,mid+1,high);
    		Merge(a,b,low,mid,high);
    	}
    }

    空间复杂度:辅助空间占n个单元,复杂度O(n);

    时间复杂度:每趟归并O(n),共log2n趟,时间复杂度O(nlog2n);

    稳定性:稳定;

    使用范围:适用于顺序存储;

    总结:时间复杂度为O(nlog2n)的稳定的排序算法,但需要消耗额外的空间,空间复杂度为O(n)。

    5.基数排序(桶排序)

    基数排序是一种与上述方法都不同的排序方法,上述方法都是基于对元素关键字的比较而进行排序的,基数排序是适用于多关键字,且关键字有一定范围的情况,通过“分配”与“回收”两种操作,对多个关键字中单关键字逐个进行排序,最后达到整体有序的目的。基数排序又称为桶排序,箱排序,个人觉得叫桶排序更形象。

    基数排序按照但关键字比较顺序的不同,可分成两类:最高位优先(MSD)、最低位优先(LSD),对于整数来讲,MSD和LSD是完全不同的,MSD适用于数据数位较多的情况,LSD适用于数据数位较少的情况;MSD在分配后,不立即回收桶,MSD是一个递归的算法,每次分配后,将元素多于1个的桶再分配,直至全分配为每个桶内元素均不多于1个,再回收桶,LSD则不存在这个问题,每次在分配后立即按序回收。

    算法思想:首先根据最后一个关键字(或者第一个,根据是MSD还是LSD)的范围设置桶的数量,假设范围为r,设置r个桶,然后“分配”:根据序列中每个数据元素最后一个关键字的取值,依次(元素的相对顺序不变)扔进对应的桶里;“回收”,按照关键字的取值顺序将各个桶内的元素首尾相接形成新的序列,这个序列就是按照最后一个关键字排序的序列。然后再根据倒数第二个关键字“分配”和“回收”,直至全部完成。所谓的关键字的范围,对于10进制整数来讲,范围为0-9;

    1)LSD

    举例说明,序列{321,852,753,416,085,009},按照从小到大递增顺序排序。

    分析:最高位为百位,所以有个位、十位、百位三个关键字,十进制数,每个关键字的范围为0-9,则每次分配10个桶,共需分配3次,按照LSD算法,第一次按照个位分配,第二次按照十位分配,第三次按照百位分配。

    2)MSD

    一样的例子,序列{321,852,753,416,085,009},按照从小到大递增顺序排序。

    分析:最高位为百位,所以有个位、十位、百位三个关键字,十进制数,每个关键字的范围为0-9,则每次分配10个桶,共需分配3次,按照MSD算法,第一次按照百位分配,如果有桶内元素多余1个,再按十位分配。

    void RadixSort(int a[],int len){
    	int i,j,max=a[0],r=0,d;
    	//创建10个桶
    	int bucketId,bucketLen;
    	int k,p;
            //使用二维数组作为桶(队列)
    	int **buckets=(int **)malloc(sizeof(int *)*10);
    	for(i=0;i<10;i++){
    		buckets[i]=(int *)malloc(sizeof(int)*(len+1));//多设一个位置,存当前桶内的元素个数
    		buckets[i][0]=0;//设置为空桶
    	}
    	//求最大值
    	for(i=1;i<len;i++)
    		if(max<a[i])
    			max=a[i];
    	//求最大位数
    	while(max!=0){
    		r++;
    		max=max/10;
    	}
    	//扔进桶
    	d=1;
    	for(i=0;i<r;i++){
    		//分配
    		for(j=0;j<len;j++){
    			bucketId=(a[j]/d)%10;
    			bucketLen=buckets[bucketId][0];
    			buckets[bucketId][bucketLen+1]=a[j];
    			buckets[bucketId][0]++;
    		}
    		//回收,回收时候注意要把桶清空。这里我直接把桶内的元素个数设置为0
    		j=0;
    		for(k=0;k<10;k++){
    			bucketLen=buckets[k][0];
    			if(bucketLen>0){
    				for(p=1;p<=bucketLen;p++)
    					a[j++]=buckets[k][p];
    			}
    			buckets[k][0]=0;//!!清空桶!!!!
    		}
    		d=d*10;//每次取得的位数慢慢增高
    	}
    	free(buckets);
    }

    空间复杂度:与关键字的范围有关,若范围为r,需要r个辅助队列,复杂度O(r);

    时间复杂度:与关键字的个数有关,若关键字个数为d(整数就是指位数),每趟分配为O(n),每趟回收为O(r),分配d趟,时间复杂度为O(d(n+r));

    稳定性:稳定;

    使用范围:适用于顺序存储,链式存储;

    总结:执行效率与序列初始状态无关,是基于多关键字的排序,关键字必须具有一定的范围,若范围太大,效率反而会降低,也不适用。

    三、内部排序总结

         算法名称 空间复杂度 时间复杂度 稳定性
    直接插入排序 O(1) O(n^2) 稳定
    折半插入排序 O(1) O(n^2) 稳定
    希尔排序 O(1) <O(n^2)(基于增量函数,最坏n^2) 不稳定
    冒泡排序 O(1) O(n^2) 稳定
    快速排序 O(log2n)(利用工作栈) O(nlog2n) 不稳定
    简单选择排序 O(1) O(n^2)(固定不变) 不稳定(低级排序中,唯一不稳定)
    堆排序 O(1) O(nlog2n) 不稳定
    归并排序 O(n)(合并操作创建辅助数组) O(nlog2n) 稳定(高级排序中,唯一稳定)
    基数排序 O(r)(关键字范围,桶的数量) O(d(n+r))(d躺分配,r个桶) 稳定

    1)高级排序中(O(nlog2n)),就平均性能而言,快速排序性能最佳,时间最省,但快速排序在最坏情况下不如堆排序和归并排序,而堆排序和归并排序,堆排序空间消耗更少,在n较大时,归并更快,但消耗辅助空间更多。

    2)简单排序中(O(n^2)),冒泡排序和直接插入排序在最好的情况下可以达到O(n),其中直接插入排序最为简单,当基本有序或者n较小时,使用直接插入排序最佳;当n较小时,如果元素本身信息量较大时,为减少元素移动消耗的时间,选择简单选择排序较好。

    四、外部排序

    外部排序,数据结构里只介绍了多路归并排序,然后由此延申,提出了三个提升归并效率的算法。

    因为内存比较小,在处理海量数据时,数据没有办法全部同时装入内存,导致只能利用外部排序归并算法将数据不断在内存和外存间写入写出,以达到排序的目的。在外部排序时,影响排序效率的主要因素是写入写出的时间,也即IO次数越多,耗时越久。

    算法思想:在外部排序时,首先将数据分成一个一个小的数据段,再将数据段装入内存利用内部排序算法进行排序,再写回外存,这个过程结束后,形成一个又一个的有序段,称为归并段。然后就是利用归并排序的思想,需要不断将归并段进行归并排序,如m路归并,每次从m个归并段各取一个元素装入内存比较,每次取出一个最小元素,输出到外存,然后取出元素所在的归并段下一个元素补上,再比较,再输出,以此类推。

    1.置换选择排序

    如果分成的归并段太多,那么需要多次归并,增加了归并趟数,同时增加了写入写出时间的消耗,由此引出了置换选择算法,该算法可以减少归并段的数量,甚至可以使归并段的大小(元素个数)超过内存。

    算法思想:(假设递增)

    1)将待排序序列元素依次放入内存,直至填满内存;

    2)此时选内存中关键字最小的元素输出到外存加入有序序列,并将其关键字记录下来记为max;

    3)从待排序序列中取出下一个元素放入内存,补上空出的位置;再次执行2操作,如果要输出元素的关键字小于max,执行4操作;

    4)将该元素进行标记(此时内存中的最小值),选取内存中未标记过的关键字最小的元素输出到外存加入有序序列,执行3操作,直至内存中所有的元素都已经被标记,此时,已经输出的有序序列作为第一个归并段(长度可能远大于内存大小),将内存中元素的标记全部抹去,按照2-4的顺序,输出下一个归并段。

    举例,8,3,1,9,7,2,6,4,5,按递增排序,内存可容纳3个数,采用置换选择排序生成归并段。

    2.最佳归并树

    利用置换选择排序后,得到的是大小不等的归并段,假设m路归并,每一趟归并,都要让m个归并段参加,那么第一趟就参与归并操作的归并段中的元素,后面每一趟排序都要再次参与归并,这样的话,如果第一次归并时,归并段的元素太多,那么后面每一趟归并所需要的IO次数就会很多,所以最佳归并树,就是利用哈夫曼树的原理,让归并树的带权路径最短,从而减少总的IO次数,提升系统排序效率的方法。

    最佳归并树是一棵只有度为0和度为m的结点的严格m叉树(假设m路归并)。将每一个归并段当作一个叶子结点,每个归并段的长度作为该结点的权值,在构建最佳归并树时,和构建哈夫曼树不同的是,需要先计算一下所给的结点(归并段数量)是否可以正好构成一棵严格的m叉树,如果不能构成的话,需要添加权值为0的结点,使其可以刚好构成一棵严格的m叉树,首先计算u=(n0-1)%(m-1)(n0为叶子结点的数量,也就是归并段的段数),如果u为0,则说明刚好可以构成一棵严格的m叉树,若u不为0,计算m-u-1的值,这个值就是应该添加的0结点的数量。

    举例,8个初始归并段,其长度分别为8,3,9,7,2,6,4,5,采用3路归并排序,构造最佳归并树。

    u=(8-1)%(3-1)=1,表示需要添加3-1-1=1个0结点,构造最佳归并树过程如下。

    3.败者树

    每一趟归并排序,从需要归并的归并段中各取出一个元素,放入内存进行比较,每次取出其中的关键字最小(大)的元素,如果有N个元素,那么需要比较N-1次,当数据量很大时,这是很耗时间的,而败者树是用来在归并排序中减少元素间比较次数的。败者树的思想是,构造一棵完全二叉树,每个叶子结点存放各个参与归并排序需要比较的元素,内部结点用来记忆左右子树的失败者,让胜者往上继续比较(作为败者结点的父节点),一直到根节点,根节点指向最小(大)的元素。

    假设,归并段为(8,5,2),(4,1,6),(3,7),初始装入内存为,8,4,3;

    后面就不细讲了,依次选出最小的元素,只举第一二轮作为例子,在第一轮结束后,第三个归并段内的7补上,此时,8与4不用再重新比较了,只需要不叫4与7的大小,就可以选出最小元素为4,减少了比较的次数。

    因此,n个元素,m路归并,初始归并段数为r,所需的归并趟数s=logm(r),使用败者树,每m个元素挑选1个最小值需要比较⌈log2m⌉ 次(败者树的深度),故每一趟归并(n个元素参加),只需要比较(n-1)⌈log2m⌉ 次(败者树的深度),故总的比较次数为S(n-1)⌈log2m⌉=⌈logm(r)⌉(n-1)⌈log2m⌉=(n-1)⌈log2r⌉,比起不使用败者树的情况,共需要比较⌈logm(r)⌉(n-1)(m-1)次,使用归并树,减少了IO次数,提高了效率。

     

    参考书:《2020年数据结构考研复习指导》王道论坛,《数据结构》严蔚敏。

    (有不妥不对之处,欢迎留言指出,一起进步~有些算法还看不懂我觉得可以去B站搜相关视频看)

    展开全文
  • 数据结构排序总结

    千次阅读 2015-11-07 09:03:23
    0.2) 本文列出了数据结构中基本上所有的数据结构排序算法, 整理了相关的博文(源代码); 0.3)对于数据结构排序的遗憾是, 这个排序,哥子已经搞了整整1周了,搞乏了,所以没有实现 以 Sedgewick 增量序列 { ...
  • 数据结构排序算法之插入排序<一> 插入排序(Insertion sort) 是一种简单直观且稳定的排序算法。如果有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,...
  • 数据结构排序算法系列】数据结构八大排序算法

    万次阅读 多人点赞 2016-03-25 22:36:40
    如Windows操作系统的文件管理中会自动对用户创建的文件按照一定的规则排序(这个规则用户可以自定义,默认按照文件名排序)因此熟练掌握各种排序算法是非常重要的,本博客将对数据结构中常见的八大排序算法进行详细...
  • 数据结构 排序习题

    千次阅读 2018-05-23 22:36:53
    10数据结构复习题(排序) 一.判断题(下列各题,正确的请在前面的括号内打√;错误的打╳ ) ( )(1)如果某种排序算法不稳定,则该排序方法就没有实用价值。( )(2)希尔排序是不稳定的排序。( )(3)...
  • 数据结构排序算法之堆排序

    万次阅读 2016-03-11 16:45:26
    关于堆排序的相关知识非常复杂,不懂得可以参考任意一本数据结构教程,本博客只对堆排序框架及代码进行讲解。 堆排序分三个大的步骤:建初堆,堆调整,堆排序(其中最核心的是堆调整) 1建初堆:从数组中的最后一个非...
  • 数据结构排序之快速排序算法

    千次阅读 多人点赞 2019-06-01 18:49:45
    排序中快速排序是在冒泡排序的基础上进行算法优化而得出的,很大程度上见减小了冒泡排序的时间复杂度。可以说快速排序算法无愧于它的名字,高速高效。 接下来将介绍快速排序的思想以及相应算法 快速排序 快速...
  • 数据结构排序算法总结

    千次阅读 2019-07-03 20:51:49
    它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要...
  • 数据结构 排序 思考题2

    千次阅读 2020-04-13 21:18:07
    全部每周作业和视频思考题答案和解析见浙江大学 数据结构 思考题+每周练习答案 题目一:在堆排序中,元素下标从0开始。则对于下标为i的元素,其左、右孩子的下标分别为: A. 2i-1, 2i B. 2i, 2i+1 C. ...
  • 数据结构 排序 思考题 3

    千次阅读 2020-04-20 13:30:11
    全部每周作业和视频思考题答案和解析 见浙江大学 数据结构 思考题+每周练习答案 题目一:什么是快速排序算法的最好情况? A. 初始就顺序 B. 初始就逆序 C. 每次主元的位置都在数组的一端 D. 每次...
  • 数据结构 排序 思考题1

    千次阅读 2020-04-13 15:51:58
    全部每周作业和视频思考题答案和解析 见浙江大学 数据结构 思考题+每周练习答案 题目1: 对于7个数进行冒泡排序,最坏情况下需要进行的比较次数为 A. 7 B. 14 C. 21 D. 49 第一轮冒泡,索引 1 ...
  • 数据结构 排序 思考题4

    千次阅读 2020-04-20 17:14:10
    题目一:(表排序后的)物理排序过程的最坏情况是: A. 有2个环,每个包含N/2个元素 B. 有N/2个环,每个环包含2个元素 C. 只有1个环,包含了全部N个元素 D. 不知道 B显然是最坏的情况(再想一下...
  • 数据结构排序算法—插入排序

    千次阅读 2016-10-11 08:40:56
    插入排序又分为直接插入排序和希尔排序,以下为其两种排序方式的Java实现代码 package com.method; public class InsertMethod { /** * 直接插入排序 * @param arr * @return */ public int[] ...
  • 数据结构排序之桶排序

    千次阅读 2014-10-24 01:49:33
    写完了这个桶排序,赶紧睡觉了,明天还要上课,程序猿的
  • 数据结构排序之快速排序

    千次阅读 2014-10-24 00:35:38
    本来昨天晚上就可以把快速排序发上去,没想到昨晚CSDN竟然维护,那么就只能现在在这烂网速的情况下发
  • 第一趟排序过后最小元素就会移到待排序元素最前面,这样每趟排序过后剩余待排序元素的最小值总会移到剩余待排序元素最前面,这样,n个元素最多进行n-1趟就可以有序了。注意的是,只要其中一趟如果没发生交换元素的...
  • 数据结构排序算法

    千次阅读 2013-07-15 13:00:09
    冒泡排序: #include void Swap(int *a,int *b) { int tmp = *a; *a = *b; *b = tmp; } void BubbleSort(int arr[],int len) { /*需要n-1趟排序*/ for (int i = 0; i ; ++i) { for (int j = 0; j < len -
  • c语言 数据结构 排序算法小结

    千次阅读 2017-12-27 17:49:21
    本题旨在测试各种不同的排序算法在各种数据情况下的表现。各组测试数据特点如下 数据1:只有1个元素; 数据2:11个不相同的整数,测试基本正确性; 数据3:103个随机整数; 数据4:104个随机整数; 数据5:105...
  • 数据结构排序实验,共有冒泡,选择,插入,快速排序四种排序方法。c语言代码详细实现和解释。
  • 数据结构排序算法综合运用及比较(C语言实现)

    千次阅读 多人点赞 2020-04-29 20:30:09
    排序算法综合及效率比较 实验目的 实验内容 ...培养综合运用所学知识,根据具体问题进行数据结构设计和算法设计的能力。 (2)熟练掌握简单的演示菜单与人机交互设计方法。 2.实验内容 (1)用r...
  • 一、 实验目的1. 掌握各种常用排序的算法...交换类排序:如:冒泡排序,快速排序,其中冒泡排序应该是计算机专业的学生最为熟悉的一种排序算法了,而快速排序则是在冒泡排序的基础上一次消除多个逆序对改进而来3...
  • 通过一趟排序将要排序数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。...
  • 实验题目: 排序算法实现与比较  实验环境: Visual C++ 6.0    实验八: 实验目的和要求:熟悉多种排序算法,理解每种排序算法思想,掌握排序算法的基本设计方法,掌握排序算法时间复杂度和空间...
  • 复习之余,就将数据结构中关于排序的这块知识点整理了一下,写下来是想与更多的人分享,最关键的是做一备份,为方便以后查阅。 排序 1、概念:  有n个记录的序列{R1,R2,.......,Rn}(此处注意:1,2,n 是下表序列,...
  • 本文整理了归并排序的大体思路;并用c语言进行了实现;最后将递归过程进行了较为直观的体现
  • Guava TreeMultiMap自定义数据结构排序

    千次阅读 2016-12-27 17:59:12
    TreeMultiMap 对基本类型的排序本文就不介绍了,网上到处都是。但是对自定义类型的排序网上的较少。 参照 TreeMultiMap 的定义,需要重写排序方式。本文通过对Investor的ld对Investor进行排序。 public ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 68,638
精华内容 27,455
关键字:

数据结构排序

数据结构 订阅