【算法】の基础——常见排序算法

排序算法

常见排序算法总览

这里写图片描述

快速排序

public static void quickSort(int[] array,int low,int high){
		int l = low;//l表示本次需要排序的数组最低位。
		int h = high;//h表示本次需要排序的数组最高位。
		int flag = array[low];//以本次需要排序的数组的最低位为flag开始。
		
		//如果l和h在移动的过程中相等了,就要停止。所以移动和交换的过程要在l<h的条件下进行,并且时刻进行检查。
		while(l<h){
			
			//从h位开始,如果比flag大就将h向左移动,即h--。
			while(l<h && array[h]>=flag){
				h--;
			}
			//如果h位比flag小,则需要交换,在此处进行。
			if(l<h && array[h]<flag){
				int temp = array[h];
				array[h] = array[l];
				array[l] = temp;
				l++;
			}
			//从l位开始,如果比flag小就将l向右移动,即l++。
			while(l<h && array[l]<=flag){
				l++;
			}
			//如果l位比flag大,则需要交换,在此处进行
			if(l<h && array[l]>flag){
				int temp = array[h];
				array[h] = array[l];
				array[l] = temp;
				h--;
			}
			
		}
		
		//一趟结束,判断flag两端的子数组是否需要排序
		if(l>low){
			quickSort(array, low, l-1);
		}
		if(h<high){
			quickSort(array, h+1, high);
		}
		
	}

堆排序

https://blog.csdn.net/qq_28063811/article/details/93034625/

  public static void heapSort(int[] array) {
    for (int i = array.length - 1; i > 0; i--) {
      createMaxHeap(array, i);
      swap(array, i, 0);// 注意是与i交换
    }
  }

  private static void createMaxHeap(int[] array, int lastIndex) {
    // 完全二叉树中,最后一个非叶子节点的index
    int lastParentIndex = (lastIndex - 0) / 2 - 1;
    for (int i = lastParentIndex; i >= 0; i--) {
      // 在根节点和左右孩子中,把最大的作为根
      int leftChildIndex = 2 * i + 1, rightChildIndex = 2 * i + 2;
      if (leftChildIndex <= lastIndex && array[leftChildIndex] > array[i]) {
          swap(array, i, leftChildIndex);
      }
      if (rightChildIndex <= lastIndex && array[rightChildIndex] > array[i]) {
          swap(array, i, rightChildIndex);
      }
    }
  }
	

插入排序

	public static void main(String[] args) {
		int[] array = {33,12,25,46,33,68,19,80};
		
		for(int i=1;i<array.length-1;++i){
			//每次选取一个数作为标志
			int flagNum = array[i];
			
			//从标志数的位置向前遍历,插入到合适的位置上
			for (int j = i-1; j >= 0 ; j--) {
				if(array[j]<flagNum){
					break;
				}
				array[j+1] = array[j];
				array[j] = flagNum;
			}
		}

		//打印结果
		for (int i : array) {
			System.out.print(i+" ");
		}
	}

希尔排序

public static void shellSort(int[] array) {

        int d = array.length;
        while (d >= 1) {
            d /= 2;//计算本次的增量
            for (int i = 0; i < d; i++) {//该增量情况下,有多少个需要排序的子数组

                for (int j = i + d; j < array.length; j += d) {//对每个子数组进行插入排序
                    for (int k = j - d; k >= 0; k--) {
                        if (array[k] < array[j]) {
                            MyUtil.swap(array, k, j);
                        }else {
                            break;
                        }
                    }
                }
            }
        }
    }

选择排序

	public static void sort(int[] arr){
		
		for(int i=0 ; i<arr.length-2 ; ++i){
			//用flag记录本趟遍历的位置
			int flag = i;
			//遍历还未排序的数字,用flag记录最大的位置
			for(int j=i+1 ; j<arr.length-1;j++){
				if(arr[flag]>arr[j]){
					flag = j;
				}
			}
			//把flag位置与本趟排序的起点位置的数字交换
			int t = arr[i];
			arr[i] = arr[flag];
			arr[flag] = t;
		}
		
	}

基数排序


    private static void radixSort(int[] array) {
        //创建桶
        Queue<Integer>[] container = new LinkedList[]{new LinkedList(),new LinkedList(),new LinkedList(),new LinkedList(),new LinkedList(),new LinkedList(),new LinkedList(),new LinkedList(),new LinkedList(),new LinkedList()};
        
        int times = getHighestDigit(array);
        for (int i = 0; i < times; i++) {
            //装入桶
            for (int j = 0; j < array.length; j++) {
                int targetNum = getNumAtDigit(array[j], i);
                container[targetNum].offer(array[j]);
            }
            //从桶取出
            int index = 0;
            for (int j = 0; j < container.length; j++) {
                while (container[j].size()>0){
                    array[index] = (int) container[j].poll();
                    index++;
                }
            }
        }

    }

    //获取数组最大数的位数
    private static int getHighestDigit(int array[]) {
        int max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (array[i]>max){
                max = array[i];
            }
        }
        return String.valueOf(max).length();
    }

    // 获取number第digit位上的数字
    // digit starts from 0
    private static int getNumAtDigit(int number, int digit) {
        int result = 0;
        for (int i = 0; i <= digit; i++) {
            result = number%10;
            number = number/10;
        }
        return result;
    }

归并排序

public static void mergeSort(int[] array, int left, int right) {

        if (left < right) {
            int mid = (left+right)/2;;
            mergeSort(array, left, mid);
            mergeSort(array, mid + 1, right);
            merge(array, left, right);
        }
    }

    private static void merge(int[] arr, int left, int right) {
        int mid = (left+right)/2;
        int[] resArr = new int[right - left + 1];
        int indexA = left;
        int indexB = mid+1;
        for (int i = 0; i < resArr.length; i++) {
            if (indexA > mid){
                resArr[i] = arr[indexB];
                indexB++;
                continue;
            }
            if (indexB > right){
                resArr[i] = arr[indexA];
                indexA++;
                continue;
            }
            if (arr[indexA]<arr[indexB]){
                resArr[i] = arr[indexA];
                indexA++;
            }else {
                resArr[i] = arr[indexB];
                indexB++;
            }
        }
        for (int i = left,j=0; i <= right; i++,j++) {
            arr[i] = resArr[j];
        }

    }

排序稳定性分析

摘自百度百科
(1)冒泡排序
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改 变,所以冒泡排序是一种稳定排序算法。
(2)选择排序
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个 元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么 交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
(3)插入排序
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开 始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相 等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳 定的。
(4)快速排序
快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j, 交换a[i]和a[j],重复上面的过程,直到i>j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。
(5)归并排序
归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有 序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定 性。那么,在短的有序序列合并的过程中,稳定是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结 果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。
(6)基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优 先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。
(7)希尔排序(shell)
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元 素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
(8)堆排序
我们知道堆的结构是节点i的孩子为2i和2i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n /2-1, n/2-2, …1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值