《算法》学习笔记(2)—— 排序

一:基本概念

假设含有n个记录的序列为{r1,r2,……,rn},其相应的关键字分别为{k1,k2,……,kn},需确定1, 2,……,n的一种排列p1,p2,……,pn,使其相应的关键字满足kp1<=kp2<=……<=kpn(非递减或非递増)关系,即序列成为一个按关键字有序的序列 {rp1,rp2,……,rpn},这样的操作就称为排序。

排序的依据是关键字直接的大小关系,可以是主关键字,也可以是次关键字。

二:排序的稳定性

假设 ki = kj ( 1 <= i <= n ,1 <= j <= n , i != j ) , 且在排序前的序列中 ri 领先于 rj ( 即i<j )。 如果排序后 ri 仍领先于 rj ,则称所用的排序方法是稳定的;反之,若可能使得排序后的
序列中 rj 领先 ri ,则称所用的排序方法是不稳定的。

也就是说相等的值在排序前后他们的相对位置保持不变

三:分类

  • 内排序是在排序整个过程中,待排序的所有记录全部被放置在内存中。
  • 外排序是由于排序的记录个数太多,不能同时放置在内存,整个排序过程需要在内外存之间多次交换数据才能进行。

四:影响因素

  • 时间性能:髙效率的内排序算法应该是具有尽可能少的关键字比较次数和尽可能少的记录移动次数。
  • 辅助空间:辅助存储空间是除了存放待排序所占用的存储空间之外,执行算法所需要的其他存储空间。
  • 算法的复杂性

五:内排序具体算法

1. 冒泡排序——不断两两比较

(1)定义:

冒泡排序一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

(2)特点:
  • 时间复杂度 = O(n2),空间复杂度 = O(1)
  • 比较次数和交换次数:因为当进行第 i 趟的时候,前面已经有 i-1 个元素有序,而每一趟排序都需要对后面的n-(i-1)个元素进行比较,所以需要比较 n-i 次。因为在比较时两两交换,所以需要 n-i 次交换。
  • 总比较次数 = (n-1)+(n-2)+(n-3)+…+3+2+1 = n*(n-1)/2,总移动次数和总比较次数相同。
实现1:效率低的两两比较法
思想:
  1. 从第一个位置开始,逐步让每一个位置的关键字,都和它后面的毎一个关键字比较
  2. 如果比后面的大则交换,交换后该位置(也就是比较小的数)继续和后面的数比较。
  3. 结果每个位置的关键字在一次循环后一定变成最小值。
for (int i = 1;i < L.length;i++ )
{
	for (int j = i+1;j < L.length;j++ )
	{
		if (L[i] > L[j])
			swap(L,i,j) ; //交换值
	}
}

实现2:效率较高的冒泡排序(后面有序)
思想:
  1. 在实现1的基础上,逆向比较。从最后一个位置,逐个向前比较,直到到达前面已经排序好的位置
  2. 如果后一个比前一个小,则交换。交换后该位置继续与前面的比较。
  3. 结果每个位置的关键字在一次循环后一定变成最小值。并且后面的数字也有了一定的顺序。
for (int i = 1 ; i < L.length ; i++ )
{
	for (int j = L.length-1; j >= i ; j-- )
	{
		if (L[j] > L[j+1])
			swap(L,j,j+1) ; //交换值
	}
}

实现3:改进的冒泡排序(避免无意义循环)

使用标记,如果已经发生交换,则可以继续循环,否则则证明后面已经有序了,不需要循环。

boolean flag = true;
for (int i = 1 ; i < L.length && flag; i++ )
{
	flag = false;
	for (int j = L.length-1 ; j >= i ; j-- )
	{
		if (L[j] > L[j+1])
			{
				swap(L,j,j+1) ; //交换值
				flag = true; 	//继续循环
			}
	}
}

2. 简单选择排序——一次到位

(1)定义:

是通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i (1<=i<=n)个记录交换之。

(2)特点:时间复杂度与输入无关,移动次数最少
  • 时间复杂度 = O(n2),空间复杂度 = O(1)
  • 比较次数和交换次数:因为当进行第 i 趟的时候,前面已经有 i 个元素有序,而每一趟排序都需要对后面的 n-i个元素进行比较,所以需要比较 n-i 次。交换次数最小为0,最大为每趟移动3次,在前(n-1)/2趟移动,另外一半无需移动,总共3*(n-1)/2
  • 总比较次数 = (n-1)+(n-2)+(n-3)+…+3+2+1 = n*(n-1)/2
(3)思想:
  1. 从第一个位置开始,对该位置之后的所有元素,比第一个元素小的最小元素的坐标。
  2. 如果坐标与第一个位置相同,则证明该元素不用变
  3. 否则,交换该元素和坐标的元素
(4)实现:
    //直接选择排序
    public static int[] selection_sort(int[] arr){
        int min;
        for (int i = 0; i < arr.length ; i++) {
            min = i;
            for (int j = i + 1; j < arr.length; j++)  //开始比较
                if(arr[min] > arr[j])                    //如果比待比较的还小,
                    min = j;                          //记录更小的值的下标
            if(min != i)                              //后面有数字比当前的数字还小,交换
                SortUtil.swap(i,min,arr);
        }
        return arr;
    }

3. 直接插入排序——不断插入(性能最好的简单排序)

(1)定义:

将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数増1的有序表。

(2)特点:时间复杂度与输入有关
  • 时间复杂度 = O(n2),空间复杂度 = O(1)
  • 比较次数和交换次数:因为当进行第 i 趟的时候,前面已经有 i 个元素有序,需要对前面的 i 个元素进行比较,所以比较 i 次。因为在移动时还需要取出和插入元素,所以需要 i+1 次交换。
  • 总比较次数 = 1+2+3+…+n-1=n*(n-1)/2,总移动次数 = 3+4+5+…+n=(n+3)()n-2)/2
(3) 思想:
  1. 从第一个数开始,逐个判断。如果数字比前面的大则不用插入,否则需要插入前面的有序表
  2. 对有序表进行逆向判断,如果插入的数比这个数小,则这个数需移动一个位置
  3. 直到插入的数比这个数大,直接插入
  //直接插入排序
    public static int[] insert_sort(int[] arr){
        for (int i = 1; i < arr.length; i++) {
            if(arr[i] < arr[i-1])               //比前面的数小
            {
                int temp = arr[i];
                int j;
                for (j = i; j > 0 && temp < arr[j-1] ; j--)   //找到合适的位置
                    arr[j] = arr[j-1];
                arr[j] = temp;          //把数字插进去
            }
        }
        return arr;
    }

4. 希尔排序——改进插入排序

(1)定义:

将原本有大量记录数的记录进行分组。分割成若干个子序列,然后在这些子序列内分别进行直接插入排序,当整个序列都基本有序时,再对全体记录进行一次直接插入排序

(2)特点:
  • 不稳定排序
  • 増量序列的最后一个増量值必须等于1才行
(3)思想:
  1. 初始化增量为数组长度,开始分割并排序,直到增量为1
  2. 根据增量对数组分组,并对每个组的每个位置逐步插入排序
 //希尔排序
    public static int[] shell_sort(int[] arr){
        int partision = arr.length;
        do{
            partision = partision / 3 + 1;  //确定分割点      
            //根据分割点进行分割排序
            for (int i = partision; i < arr.length ; i += partision) {
                if(arr[i-partision] > arr[i])
                {
                    int temp = arr[i];
                    int j;
                    for (j = i; j > 0 && temp < arr[j-partision] ; j -= partision) 
                        arr[j] = arr[j-partision];
                    arr[j] = temp;
                }
            }

        } while (partision > 1);

        return arr;
    }

5. 堆排序——改进选择排序

(1)定义:

堆排序:将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移到末尾,此时末尾就是选择排序中的有序表了,然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。

(2)特点
  • 是一种不稳定的排序
  • 最大比较次数和交换次数:一棵有N个结点的树,高度最大为logN。所以插入时最多比较logN+1,删除时可能每棵子树要和孩子比较,最多比较2logN
  • 时间复杂度:O(nlogn)
(3)思想
  • 构造堆:
  1. 从根节点开始,用root标记根节点的下标,用 max 标记较大值的下标,在指定构造堆的范围内,对左右两个孩子进行查找,找到更大的孩子,用max标记。
  2. 如果根节点比孩子大,则不用换。否则把用儿子替代根节点,同时root改为被交换的儿子的下标,便于后面交换。
  3. 继续对儿子的儿子进行查找,不断与儿子比较或交换。
  4. 最终,root标记的是最小的孩子的下标,在这里交换根节点。
//构造堆
    public HeapPQ(T[] arr) {
        heap = (T[])new Object[arr.length + 1];
        for (int i = 0; i < arr.length; i++) {
            swim(i+1,arr[i]);
        }
    }
    //上浮
    public  void swim(int index,T key) {
        if(index > heap.length - 1)
            resize(heap.length*2);
        heap[index] = key;  //先将待检查的记录放入堆中
        size++;
        while (index > 1 && compareTo(heap[index],heap[index/2]) > 0) {  //如果记录比根结点大,则需要上浮
            int max = index;
            if(index/2 == 1 && compareTo(heap[index],heap[index - 1]) < 0)   //找出需要上浮的结点或兄弟结点
                max = index - 1;
            swap(max,index /= 2,heap); //上浮
        }
    }



  • 排序:将堆顶记录与最后一个未排序的交换,再重新调整堆。
    //删除最大值
    public T delMax(){
        T max = heap[1];
        swap(1,size,heap);
        heap[size--] = null;
        sink(1);
        return max;
    }
     //下沉
    public  void sink(int key){
    if(size > 0 && size == heap.length/4)
            resize(heap.length/2);       
        while (2 * key <= size) {    //在树的范围内找
            int j = 2 * key;
            if(j + 1 <= size && compareTo(heap[j],heap[j+1]) < 0) //找出较大的孩子
                j++;
            if(compareTo(heap[key],heap[j]) >= 0)     //如果根节点比较小就下沉
                break;
            swap(key,j,heap);
            key = j;
        }
    }


6. 归并排序

(1)定义:

将含有n个记录的初始序列看成是n个有序的子序列,每个子序列的长度为1。然后两两归并,得到【n/2】个长度为2或1的有序子序列。再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。

(2)特点:需要一定的空间
  • 稳定的算法
  • 比较次数和交换次数:归并排序相当于将记录当成一棵完全二叉树,这棵树有logn层,第k层有2k-1个数组,每个数组有n/k个记录,最多需要比较 logn* 2logn-1* n /logn =nlogn次
  • 占用内存 =n+logn
  • 时间复杂度 = nlogn
(3)思想:
实现1:递归、自顶向下的归并排序
  • 合并:
  1. 以low,mid,high表示起始点,分割点,结束点,以数组old表示原来的数组,以temp表示临时数组
  2. 首先将old中记录由小到大归并入temp:从a到b遍历子数组1,从b到c遍历子数组2,两两比较,将较小的值放入temp,同时该子数组和temp的下标加一。
  3. 然后将剩余的两个数组的元素复制到temp中
  4. 把temp数组复制到old中
    private static int[] temp;	//辅助数组
 //合并
    public static void merge(int[] old,int low,int mid,int high){
        int index = 0,i,j;
        for (i = low,j = mid+1; i <= mid && j <= high;) //先比较两个子数组的大小
            if(old[i] < old[j])
                temp[index++] = old[i++];
            else
                temp[index++] = old[j++];
        while (i <= mid)        //将剩下的子数组1放进新数组中
            temp[index++] = old[i++];
        while (j <= high)       //将剩下的子数组2放进新数组中
            temp[index++] = old[j++];
        for (int k = 0; k < index; k++) {     //覆盖之前的数组
            old[k+low] = temp[k];
        }
    }

  • 排序:
  1. 对原来的数组进行平分,并对左右两个数组递归排序
  2. 如果数组中只有一个元素,则无需排序,递归结束。
  3. 将上一步的两个有序数组合并为一个
//归并排序
    public static void merge_sort(int[] arr,int low,int high){
        if(low >= high) //数组中只剩下一个元素,无法划分
            return;
        else
        {
            int mid = (low + high) / 2; //原来的数组对半分,分别排序
            merge_sort(arr,0,mid);
            merge_sort(arr,mid+1,high);
            merge(arr,low,mid,high);    //排序后再合并到原来的数组中
        }
    }
实现2:改进的归并排序
  1. 对小规模数组改用插入排序
  2. 测试数组是否有序,如果有序就无需重新合并
  //归并排序
    public static void merge_sort(int[] arr,int low,int high){
//        if(low >= high) //数组中只剩下一个元素,无法划分
//            return;
        if(high - low <= 10)    //当数组中的元素数量过少时改用插入排序
        {
            InSertSort.insert_sort(arr);
            return;
        }
        else
        {
            int mid = (low + high) / 2; //原来的数组对半分,分别排序
            merge_sort(arr,0,mid);
            merge_sort(arr,mid+1,high);
            if(arr[mid] > arr[mid+1])	//无序则排列
                merge(arr,low,mid,high);    //排序后再合并到原来的数组中
        }
    }
实现3:非递归、自底向上的归并排序
  • 排序:
  1. 将原来的数组从最小的序列开始,以2的n次方的长度翻倍
  2. 对两个子数组进行合并,放入新数组中
  //自底向上的归并排序
    public static void merge_sort(int[] arr){
        for (int i = 1; i < arr.length; i *= 2)     //子数组由不断向上增加一倍
            for (int j = 0; j < arr.length - i; j += i*2)   //对两个子数组进行归并排序
                merge(arr,j,j+i-1,Math.min(j+i*2-1,arr.length-1));  //如果范围超过数组长度,则只能在数组范围内排序
    }

7. 快速排序——改进冒泡排序(性能最好的复杂排序)

(1)定义:

通过一趟排序分割记录为独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

(2)特点:
  • 最好时间复杂度 = O(nlogn) 最坏时间复杂度 = O(n2)
  • 最好空间复杂度 = O(logn) 最坏空间复杂度 = O(n)
  • 不稳定的排序
  • 需要大量的辅助空间
(3)思想:
实现1:固定枢轴的快速排序
  • 分割:
  1. 初始化枢轴为第一个记录,初始化两个数a和b分别表示最小值和最大值的下标
  2. 在表的范围内,如果右边的表的记录比枢轴大的话,则下标b左移,直到右边的表的记录比枢轴小。此时b表示右边最靠近枢轴并且比枢纽小的下标
  3. 将a和b的数值交换,将数值较小的放在左边。
  4. 在表的范围内,如果左边的表比枢轴小的话,则下标a右移,直到左边的表的记录比枢轴大。此时a表示左边最靠近枢轴并且比枢纽大的下标
  5. 将a和b的数值交换,将数值较大的放在右边。
  6. 当最小值的下标a超过最大值的下标b,结束循环,此时a表示枢轴
int Partition (int[] L,int low,int high)
(
	int pivotkey;
	pivotkey = L[low];
	while (low < high)
	{
		while (low < high && L[high] >= pivotkey)
			high--;
		swap ( L, low, high ); /*将比枢軸记彔小的记采交换到低端*/
		while (low < high && L[low] <= pivotkey)
			low++;
		swap ( L, low, high ); /*将比枢轴记彖大的记彖交换到高端*/
	}
return low;
}

  • 排序:
  1. 根据数组找出枢轴,使得左边的数都小,右边的数都大。
  2. 对左边的数组继续递归排序
  3. 对右边的数组继续递归排序
  //快速排序
    public static int[] quick_sort(int[] arr,int low,int high){
        int pivot;
        if(low < high)
        {
            pivot = find_pivot(arr,low,high);       //获取基准值的下标,便于分割数组
            quick_sort(arr,low,pivot-1);
            quick_sort(arr,pivot+1,high);
        }
        return arr;
    }
实现2:改进的快速排序
  1. 使用三数取中:对左边,中间,右边三个数排序,再确定枢轴为开始的值。
  2. 减少交换次数:在找枢轴的位置时,找到最大或最小的数时不要交换,直接替换
  3. 对于小数组,使用插入排序
  4. 随机打乱数组,避免数组有序
  //三分法,找出low,high,mid三者中的最小值,放入low中
    public static int[] get_pivot(int[] arr,int low,int high){
        int mid = (low + high)/2;
        if(arr[high]<arr[mid])  //mid应该比high小
            SortUtil.swap(high,mid,arr);
        if(arr[low]>arr[mid])   //low应该比mid小
            SortUtil.swap(low,mid,arr);
        return arr;
    }
   //找基准值的下标
    public static int find_pivot(int[] arr,int low,int high){
        get_pivot(arr,low,high);                 //通过三分法确定基数值
        int pivot = arr[low];
        while (low<high)
        {
            while (low < high && pivot <= arr[high])    //找到比基准值小的数,放到前面
                high--;
            arr[low] = arr[high];
            while (low < high && pivot >= arr[low])     //找到比基准值大的数,放到后面
                low++;
            arr[high] = arr[low];
        }
        arr[low] = pivot;                              //在中间位置放入基准值
        return low;
    }
  //快速排序
    public static int[] quick_sort(int[] arr,int low,int high){
        int pivot;
        if(high <= low + 10)    //对于小数组,使用插入排序
        {
            InSertSort.insert_sort(arr);
            return arr;
        }
        pivot = find_pivot(arr,low,high);       //获取基准值的下标,便于分割数组
        quick_sort(arr,low,pivot-1);
        quick_sort(arr,pivot+1,high);
        return arr;
    }
实现3:荷兰国旗问题

排序对象中有很多重复的元素,快速排序算法表现很不尽如人意

int[] Partition (int[] L,int low,int high)
(
	int less = low - 1;
	int more = high;
	while(low < high){
		if(L[low] < L[high])
			swap(L,++less,low++);
		else if(L[low] > L[high])
			swap(L,--more,low);
		else
			low++;
	}
	swap(L,more,high);
	return new int[] {less+1,more};
}
  //快速排序
    public static int[] quick_sort(int[] arr,int low,int high){
        int mid1,mid2;
        if(low < high)
        {
        	swap(arr,low+(int)(Math.random()*(high - low + 1)),high);//先随机排序
            int[] temp = find_pivot(arr,low,high);       //获取基准值的下标,便于分割数组
            mid1 = temp[0];
            mid2 = temp[1];
            quick_sort(arr,low,mid1-1);
            quick_sort(arr,mid2+1,high);
        }
        return arr;
    }

非基于比较的排序,对数据的位数和范围有限制

8. 桶排序

(1)定义:

桶排序的基本思想是: 把数组 arr 划分为 n 个大小相同子区间(桶),每个子区间各自排序,最后合并 。计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。
在这里插入图片描述

(2):特点

桶排序的最优时间复杂度为O(n),最差时间复杂度取决于桶中的排序算法,所以桶的数量越多,时间效率越高

完美情况是每个桶中只有一个数据,时间复杂度可以达到O(n),但是会浪费很大的空间

考虑时间和空间的权衡问题。在数据规模合适的情况下,桶排序的表现可以优于快排等基于比较的排序算法

(3):实现
  1. 找出待排序数组中的最大值 max、最小值 min
  2. 我们使用 动态数组 ArrayList 作为桶,桶里放的元素也用 ArrayList 存储。桶的数量为(max-min)/arr.length+1
  3. 遍历数组 arr,计算每个元素 arr[i] 放的桶
  4. 每个桶各自排序
//获取桶的序号
public static void bucket(int num,int len,int min,int max){
	return (int)((num - min) * len / (max - min));
}
//桶排序
public static void bucketSort(int[] arr){
	int max = Integer.MIN_VALUE;
	int min = Integer.MAX_VALUE;
	for(int i = 0; i < arr.length; i++){
		max = Math.max(max, arr[i]);
		min = Math.min(min, arr[i]);
	}
	
	//创建桶
	int bucketNum = (max - min) / arr.length + 1;
	ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
	for(int i = 0; i < bucketNum; i++){
		bucketArr.add(new ArrayList<Integer>());
	}
	//将每个元素放入桶
	for(int i = 0; i < arr.length; i++){
		int num = (arr[i] - min) / (arr.length);
		bucketArr.get(num).add(arr[i]);
	}
	//对每个桶进行排序
	for(int i = 0; i < bucketArr.size(); i++){
		Collections.sort(bucketArr.get(i));
	}
}

9. 计数排序

(1)定义

通过对待排序序列中的每种元素的个数进行计数,然后获得每个元素在排序后的位置。

(2)特点
  • 稳定
  • 时间复杂度为O(n+k),其中n为要排序的数的个数,k为要排序的数的最大值
  • 空间复杂度为O(k)
  • 只限于对整数进行排序
(3)实现
public static void countingSort(int[] arr){
	int max = Integer.MAX_VALUE;
	for(int i = 0;i < arr.length;i++){
		max = Math.max(max,arr[i]);
	}
	int[] bucket = new int[max+1];
	for(int i = 0;i < bucket.length;i++){
		bucket[arr[i]]++;
	}
	int i = 0;
	for(int j = 0;j < bucket.length;j++){
		while(bucket[j]-- > 0){
			arr[j++] = j;
		}
	}
}

10. 基数排序

(1)定义

将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
在这里插入图片描述

(2)特点
  • 时间复杂度:O(N*digit)
  • 空间复杂度:O(N)
  • 稳定性:稳定
(3)实现
public class radixSort {
	int a[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,};
	
	public radixSort(){
		sort(a);
		for(inti=0;i<a.length;i++){
			System.out.println(a[i]);
		}
	}
	public void sort(int[] array){
		//首先确定排序的趟数;
		int max=array[0];
		for(inti=1;i<array.length;i++){
			if(array[i]>max){
				max=array[i];
			}
		}
		int time=0;
		//判断位数;
		while(max>0){
			max/=10;
			time++;
		}
		//建立 10 个队列;
		List<ArrayList> queue=newArrayList<ArrayList>();
		for(int i=0;i<10;i++){
			ArrayList<Integer>queue1=new ArrayList<Integer>();
			queue.add(queue1);
		}
		//进行 time 次分配和收集;
		for(int i = 0;i < time;i++){
			//分配数组元素;
			for(int j = 0;j < array.length;j++){
				//得到数字的第 time+1 位数;
				int x = array[j] % (int)Math.pow(10,i+1) / (int)Math.pow(10, i);
				ArrayList<Integer>queue2=queue.get(x);
				queue2.add(array[j]);
				queue.set(x, queue2);
			}
			int count=0;//元素计数器;
			//收集队列元素;
			for(int k=0;k<10;k++){
				while(queue.get(k).size()>0){
					ArrayList<Integer>queue3=queue.get(k);
					array[count]=queue3.get(0);
					queue3.remove(0);
					count++;
				}
			}
		}
	}
}

六:排序算法的比较

方法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序O(n2)O(n)O(n2)O(1)稳定
简单选择排序O(n2)O(n2)O(n2)O(1)不稳定
直接插入排序O(n2)O(n)O(n2)O(1)稳定
希尔排序O(nlogn~n2)O(n1.3)O(n2)O(1)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
快速选择排序O(nlogn)O(nlogn)O(n2)O(logn~n)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
基数排序O(digit*(n+r进制))O(digit*(n+r进制))O(digit*(n+r进制))O(n+r)稳定
  • 平均情况:改进后的算法更好更快。一般都是用快速排序
  • 最好情况:如果记录的序列基本有序,则考虑冒泡排序和直接插入排序
  • 最坏情况:如果记录的序列复杂无序,则考虑堆排序和归并排序。
  • 辅助空间:如果内存使用量少时,可以使用堆排序
  • 稳定性:如果考虑排序稳定,可以使用归并排序+直接插入排序
  • 数据量:如果数据量小并且记录本身信息量大,使用简单排序算法即可。
  • 范围:如果知道记录的序列的范围,可以使用基数排序

库函数中排序的实现是综合排序

  • 数据量<60:插入排序
  • 数据量>60
    • 基本数据类型:快速排序
    • 对象数据类型:归并排序(稳定)

七:外排序基本算法——归并排序

  1. 把文件的记录分段读入内存,对记录段进行排序,输出到另一个文件,称为归并段。
  2. 对归并段再次进行归并,直到整个文件归并为有序段
  • 多路平衡归并——胜者树
  1. 对每两个归并段进行比较,选出最小值,作为两个归并段的父节点。
  2. 对每两个父节点进行比较,选出最小值,作为整棵树的根节点,也就是最小关键字。
  3. 输出关键字,更新包含它的归并段,让这个归并段与兄弟再次比较,不断更新最小值,找出新的最小关键字并输出。
  • 多路平衡归并——败者树
  1. 对每两个归并段进行比较,选出最小值,把比较失败的归并段的段号作为两个归并段的父节点。
  2. 对每两个父节点进行比较,选出最小值,把比较中失败的归并段的段号作为整棵树的根节点,也就是最失败的归并段。把选出来的优胜者(也就是最小关键字)的归并段的段号作为根结点的父结点。
  3. 输出关键字,更新包含它的归并段,让这个归并段与父结点再次比较,不断更新失败者,找出新的最小关键字并输出
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值