8、【算法思想】排序(插入&选择&冒泡&归并&快排)

1、插入&选择&冒泡&归并(基础排序)

1.1 插入排序

场景:打扑克。
需求:把扑克牌整理成有序的,方便出牌。
思路:把牌分成两部分:一部分是你手里的牌(已经排好序),一部分是要拿的牌(无序)。把一个无序的数列一个个插入到有序数列中。
等价于:一个有序的数组,我们往里面添加一个新的数据后,如何继续保持数据有序呢?我们只要遍历数组,找到数据应该插入的位置将其插入即可。
插入排序
如上图:我们要把右边的5放入左边排好序的数组中,只需要那5跟左边数组的值一一进行比较即可。对于比5 大的值,往数组后移动,当对比到2时发现52大,存入到2后面即可。
代码实现

 void insertSort(int[] target){
        int length = target.length;
        for (int i = 1; i < length; i++) {
            //i从1开始,表示第一个不需要排序
            int data = target[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if (target[j] > data) {
                    // 数据往后移动
                    target[j + 1] = target[j];
                }else {
                    //因为前面已经是排好序的 那么找到一个比他小的就不用找了,因为前面的肯定更小
                    break;
                }
            }
            //** 需要着重理解一下 为什么是  j+1** 
            target[j + 1] = data;
            System.out.print("第" +i +"次的排序结果为:");
            for(j = 0 ; j < length;j++){
                System.out.print(target[j]+" ");
            }
        }
 }

1.2 选择排序

选择排序的思路和插入排序非常相似,也分已排序和未排序区间。但选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。但是不像插入排序会移动数组 选择排序会每次进行交换。
如对数组:4 5 6 3 2 1进行排序
步骤拆分:
第一次:4 5 6 3 2 1 => 1和4进行交换 => 1 5 6 3 2 4
第二次:1 5 6 3 2 4 =>5和2进行交换 => 1 2 6 3 5 4

依次类推,最终返回:1 2 3 4 5 6
代码实现:

public void selectSort(int[] nums){
        int n = a.length;
        for (int i = 0; i < n; i++) {
            //最小数据的小标
            int minLoc = i;
            for (int j = i + 1; j < n; j++) {
                //一直对比选择
                if (a[j] < a[minLoc]) {
                    minLoc = j;
                }
            }
            //最小的数据,放到已排序的数组的后面
            int tmep = a[i];
            a[i] = a[minLoc];
            a[minLoc] = tmep;
        }
}

1.3 冒泡排序

冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复n次,就完成了n个数据的排序工作。
比如:对 4 5 6 3 2 1进行冒泡排序
第一次冒泡的结果:4 5 6 3 2 1->4 5 3 6 2 1 - > 4 5 3 2 6 1 -> 4 5 3 2 1 6,哪个元素的位置确定了,6
第二次冒泡的结果:4 5 3 2 1 6->4 3 5 2 1 6 -> 4 3 2 5 1 6 -> 4 3 2 1 5 6
第三次冒泡的结果:4 3 2 1 5 6->3 4 2 1 5 6 -> 3 2 4 1 5 6 -> 3 2 1 4 5 6
第四次冒泡的结果:3 2 1 4 5 6->2 3 1 4 5 6 -> 2 1 3 4 5 6
第五次冒泡的结果:2 1 3 4 5 6->1 2 3 4 5 6
代码实现

   public static void bubbleSord(int[] arr) {
       //控制共比较多少轮
       for (int i = 0; i < arr.length - 1; i++) { //i < arr.length -1 ? 因为是两个数的比较次数,比如 数组就两个数:1,2 ,只需要比较一次即可
           //比较次数
           for (int j = 0; j < arr.length - 1 - i; j++) {
               if (arr[j] > arr[j + 1]) {
                   int temp = arr[j];
                   arr[j] = arr[j + 1];
                   arr[j + 1] = temp;
                   //另外一种交换方法:
                   //a:2,b:3
                   //a = a+b
                   //b = a- b
                   //a = a- b
               }
           }
       }
   }

1.4 归并排序

其核心思想是递归和分治的思想。其主要流程如下:
归并排序
代码实现如下:

 private static void merge(int[] data, int left, int right) {
      if (left < right) {
          int mid = (left + right) / 2;
          //递归分组
          merge(data, left, mid);
          merge(data, mid + 1, right);
          // 分完了 接下来就要进行合并,也就是我们递归里面归的过程
          mergeData(data, left, mid, right);
      }
  }

  private static void mergeData(int[] data, int left, int mid, int right) {
      //临时数组,保存对比结果,空间换时间
      int[] temp = new int[data.length];
      //表示的是左边的第一个数的位置
      int point1 = left;
      //表示的是右边的第一个数的位置
      int point2 = mid + 1;
      //表示的是我们当前已经到了哪个位置了
      int loc = left;
      while (point1 <= mid && point2 <= right) {
          if (data[point1] < data[point2]) {
              temp[loc] = data[point1];
              point1 ++ ;
              loc ++ ;
          }else {
              temp[loc] = data[point2];
              point2 ++;
              loc ++ ;
          }
      }

      //用来完成剩下的数据组装
      while (point1 <= mid) {
          temp[loc ++] = data[point1 ++];
      }

      while (point2 <= right) {
          temp[loc ++] = data[point2 ++];
      }
      //排序好的数组放到老数组里面
      for (int i = left; i <= right; i++) {
          data[i] = temp[i];
      }
  }

1.5 快速排序

如 45 28 80 90 50 16 100 10
基准数:一般就是取要排序序列的第一个。
第一次排序基准数:45
从后面往前找到比基准数小的数进行对换:10 28 80 90 50 16 100 45
从前面往后面找比基准数大的进行对换:10 28 45 90 50 16 100 80

10 28 16 90 50 45 100 80
10 28 16 45 50 90 100 80
以基准数分为3部分,左边的比之小,右边比之大:{10 28 16} 45 {50 90 100 80}
到此第一次以45位基准数的排序完成。依次类推

快排和归并的对比:

  1. 归并排序的处理过程是由下到上的,先处理子问题,然后再合并。
  2. 快排其实就是从上到下,先分区,在处理子问题,不用合并。

图解
快排图解

public static void qSort(int data[], int left, int right) {

		int base = data[left]; // 就是我们的基准数,取序列的第一个,不能用data[0]
		int ll = left; // 表示的是从左边找的位置
		int rr = right; // 表示从右边开始找的位置
		while (ll < rr) {
			// 从后面往前找比基准数小的数
			while (ll < rr && data[rr] >= base) {
				rr--;
			}
			if (ll < rr) { // 表示是找到有比之大的
				int temp = data[rr];
				data[rr] = data[ll];
				data[ll] = temp;
				ll++;
			}
			while (ll < rr && data[ll] <= base) {
				ll++;
			}
			if (ll < rr) {
				int temp = data[rr];
				data[rr] = data[ll];
				data[ll] = temp;
				rr--;
			}
		}
		// 肯定是递归 分成了三部分,左右继续快排,注意要加条件不然递归就栈溢出了
		if (left < ll)
			qSort(data, left, ll - 1);
		if (ll < right)
			qSort(data, ll + 1, right);
	}

2、总结

衡量一个算法的指标:

  1. 时间效率:决定了算法运行多久(时间复杂度)
  2. 空间复杂度
  3. 比较次数&交换次数:排序肯定会牵涉到两个操作,一个比较是肯定的。也存在交换。
  4. 稳定性:相同的两个数排完序后,相对位置不变

以上排序方法对比:

排序名称时间复杂度是否稳定额外空间开销
插入排序O(n^2)稳定O(1)
冒泡排序O(n^2)稳定O(1)
选择排序O(n^2)不稳定O(1)
归并排序O(nlogn)稳定O(n)
插入排序O(nlogn)不稳定O(1)

3、如何选择

  1. 分析场景:稳定还是不稳定
  2. 数据量:数据量小的时候选什么?比如就50个数,优先选插入(5000*5000=25000000)
  3. 分析空间

综上所述,没有一个固定的排序算法,都是要根据情况分析的。但是如果你不会分析的情况下 选择归并或者快排。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值