赞
踩
即,把n个元素分为有序列和无序列,刚开始时,有序列默认是1个元素,无序列时剩余的n-1个元素,每次从无序列种取出1个元素与有序列进行排序,最终得到一个排序好的有序列。
public static void insertSort(int[] nums) {
for (int i = 1; i < nums.length; i++) {
int tmp = nums[i];
int j = i-1;
for (; j >= 0; j--) {
if (tmp < nums[j]) {
nums[j+1] = nums[j];
}else {
break;
}
}
nums[j+1] = tmp;
System.out.println("第"+ i + "次排序后结果为:" +Arrays.toString(nums));
}
}
int[] nums = {9,1,3,2,4,8,5};
把整体元素以gap为间距进行分组,每组之间进行直接插入排序,排序完之后再以gap/2继续排序,直至gap为1为止。
本质是插入排序,只不过这个插入排序和gap有关系。
跳跃式的分组排序,可以尽量把小的数据快速往前移,把大的数据快速往后移(相对于相邻分组的好处)。
//控制增量gap的方法 public static void shellSort(int[] nums) { int gap = 5; while (gap >= 1) { shell(nums,gap); gap = gap / 2; } } //真正排序的方法 public static void shell(int[] nums, int gap) { //直接插入排序+希尔 for (int i = gap; i < nums.length; i += gap) { int tmp = nums[i]; int j = i - gap; for (; j >= 0; j -= gap) { if (nums[j] > tmp) { nums[j+gap] = nums[j]; }else { break; } } nums[j + gap] = tmp; } }
{9,1,3,2,4,8,5,9,2,5,6,10}
从待排序的元素中,每次直接选择出最小的元素放入已经排好序的序列中,以此类推。
每次默认待排序的元素是最小的,与其后面的无序的序列进行比较,如果有比它小的,则进行交换,以此类推,当无序序列走完的时候,可以选出最小的放入到排序好的序列后面。
public static void select(int[] nums) { for (int i = 0; i < nums.length; i++) { for (int j = i + 1; j < nums.length; j++) { if (nums[i] > nums[j]) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } } } } //或者把交换封装成方法 public static void select(int[] nums) { for (int i = 0; i < nums.length; i++) { int min = i; int j = i + 1; for (; j < nums.length; j++) { if (nums[min] > nums[j]) { min = j; } } if (min != i) { swap(nums,min,i); } } } private static void swap(int[] nums, int min, int i) { int tmp = nums[min]; nums[min] = nums[i]; nums[i] = tmp; }
9,1,3,8,11,6,10
从小到大排序:建大根堆(堆顶元素是最大的,每次把堆顶元素与堆的最后一个元素进行交换,再重新调整堆,从而得到从小到大的序列)
从大到小排序:建小根堆(堆顶元素是最小的,每次把堆顶元素与堆的最后一个元素进行交换,再重新调整堆,从而得到从大到小的序列)
整体分2步:建堆+堆删除(都是向下调整算法)
class Solution { public int[] sortArray(int[] nums) { for (int i = (nums.length - 1 - 1) / 2; i >= 0; i--) { shiftDown(i,nums.length,nums); } int end = nums.length - 1; while (end > 0) { swap(nums,0,end); shiftDown(0,end,nums); end--; } return nums; } private void swap(int[] nums, int min, int i) { int tmp = nums[min]; nums[min] = nums[i]; nums[i] = tmp; } public void shiftDown(int parent, int len, int[] nums) { int child = parent * 2 + 1; while (child < len) { if (child + 1 < len && nums[child] < nums[child+1]) { child = child +1; } if (nums[child] < nums[parent]) { break; }else { swap(nums,child,parent); parent = child; child = parent * 2 + 1; } } } }
冒泡排序,前后两个元素两两比较,不满足排序情况则进行交换,就像冒泡一样,最后最大的元素像泡泡一样到了序列的最后面。
//冒泡排序 public static void bubbleSort(int[] nums) { for (int i = 0; i < nums.length - 1; i++) { for (int j = 0; j < nums.length - 1 - i; j++) { if (nums[j] > nums[j+1]) { swap(nums,j,j+1); } } } } private static void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }
②进阶优化版
我们发现如果某一趟没有发生元素交换则说明该序列已经有序了,因此可以设置标志位判断在一趟这元素有没有发生交换
public static void bubbleSort(int[] nums) { for (int i = 0; i < nums.length - 1; i++) { boolean flag = false; for (int j = 0; j < nums.length - 1 - i; j++) { if (nums[j] > nums[j+1]) { flag = true; swap(nums,j,j+1); } } if (flag == false) { break; } } } private static void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }
选定一个数为基准,比基准小的元素全放在基准左边,比基准大的元素全放在基准右边,左边和右边又递归重复上述过程,直到所有元素都排列在相应位置上。
是一种基于二叉树结构的交换排序
//快速排序 public static void quickSort(int[] nums) { quick(nums,0,nums.length-1); } //完成递归操作 public static void quick(int[] nums, int left, int right) { if (left >= right) { return; } int div = partition(nums,left,right); quick(nums,left,div-1); quick(nums,div+1,right); } //基准划分操作 public static int partition(int[] nums, int left, int right) { int pivot = nums[left]; while (left < right) { while (left < right && nums[right] >= pivot) { right--; } nums[left] = nums[right]; while (left < right && nums[left] <= pivot) { left++; } nums[right] = nums[left]; } nums[left] = pivot; return left; }
分析:
稳定性:不稳定(前后交换)
时间复杂度:O(nlogn)【最好,满二叉树,完全二叉树,每次都能均匀分割待排序序列】,O(n平方)【最坏,有序/逆序,相当于只有左树/右树】
空间复杂度:O(logn)【左树/右树高顿】,最坏为O(n)
对数据敏感,元素越有序,快排越低效
①递归的出口为什么要取大于号?
假设待排序序列为 1 2 3 4
那么基准为1,left为0,right为3
right开始往前走,走到0位置和left相遇,方法结束
下一次,left为0,right为-1,因此也不可以进行递归,而需直接返回,所以要取大于等于号。
②循环的条件为什么要取等号?
假设不取等号,前后2个元素相等的情况下,会陷入死循环。
例如,待排序序列为6 3 4 5 7 6
,则会在第一个元素和最后一个元素之间反复横跳。
③左边作为基准,为什么要先从右边开始走,而不先从左边走?
用Hoare法比挖坑法好解释。
最终会造成把比基准大的数换到前面去。
快排有3种方法(是一个递归过程)
①Hoare版:left去找比基准小的,right去找比基准大的,找到之后两者进行交换。
②挖坑法:right去找比基准小的,放到left空出来的坑里,left去找比基准大的,放到right空出来的坑里。
③前后指针
快排递归的次数太多了,会导致栈溢出。
用栈去模拟递归左边和递归右边
栈用来存小区间的两端的下标
要保证小区间至少有2个元素,才把区间下标入栈
然后每次出栈2个,重新定位小区间,
//快速排序非递归 public static void quickSortNot(int[] nums) { int left = 0; int right = nums.length - 1; Deque<Integer> stack = new LinkedList<>(); int div = partition(nums,left,right); //挖坑法 //判断是否有2个元素 if (left +1 < div) { stack.push(left); stack.push(div-1); } if (div + 1 < right) { stack.push(div+1); stack.push(right); } while (!stack.isEmpty()) { right = stack.pop(); left = stack.pop(); div = partition(nums,left,right); if (left +1 < div) { stack.push(left); stack.push(div-1); } if (div + 1 < right) { stack.push(div+1); stack.push(right); } } }
先两两分解,再两两合并
先将序列元素分解成为独个的,有序表中只有一个元素分解结束,再两两合并成有序的,即将已有序的子序列合并,得到完全有序的序列,先让每个子序列有序,再使子序列段间有序。
//归并排序 public static void mergeSort(int[] nums) { //先分解 divM(nums, 0 ,nums.length-1); } //递归分解 public static void divM(int[] nums, int left, int right) { if (left >= right) { return; } int mid = left + (right - left) / 2; divM(nums,left,mid); divM(nums,mid+1,right); //合并 merge(nums,left,right,mid); } //合并 public static void merge(int[] nums, int left, int right, int mid) { int s1 = left; int s2 = mid + 1; int[] tmp = new int[right - left +1]; //额外开辟的空间 int k = 0; //tmp数组的下标 while (s1 <= mid && s2 <= right) { if (nums[s1] <= nums [s2]) { tmp[k++] = nums[s1]; }else { tmp[k++] = nums[s2]; } } while (s1 <= mid) { tmp[k++] = nums[s1]; } while (s2 <= right) { tmp[k++] = nums[s2]; } //现在tmp已经有序了,但不是在原来数组上排序的 //把tmp数组放到原数组上 for (int i = 0; i < tmp.length; i++) { nums[i + left] = tmp[i]; } }
int[] tmp = new int[right - left +1]; //额外开辟的空间
不能比较数字之间的大小而进行的排序。
①找到给定序列中的最小元素值和最大元素值 ②新建一个计数数组,数组的长度由第一步找到的最小值和最大值确定的范围而定
③遍历序列数组,对应计数数组的下标对其进行计数的操作 ④遍历计数数组,将计数数组中值不是0的下标对应复制到序列数组中,即排好序了
public static void countSort(int[] nums) { int min = nums[0]; int max = nums[0]; //找最大值和最小值 for (int i = 1; i < nums.length; i++) { if (min > nums[i]) { min = nums[i]; } if (max < nums[i]) { max = nums[i]; } } //新建计数数组 int[] count = new int[max - min + 1]; //遍历序列数组,并且用计数数组计数 for (int i = 0; i < nums.length; i++) { count[nums[i] - min]++; } //遍历计数数组 int k = 0; for (int i = 0; i < count.length; i++) { while (count[i] != 0) { nums[k] = i + min; k++; count[i]--; } } }
建立0到9也就是10个基数捅, 先按照个位排序,将个位放置对应的记述桶内 再依次取出 先按照使位排序,将十位放置对应的记述桶内 再依次取出
直至最高位,循环上述操作
123 99 678 93 58 77 65 54 26
划分多个范围相同的区间
每个子区间自排序
合并
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。