插入排序
从第二个数,往前面进行插入,默认第一个数字有序,插入第二个,则前两个都有序了,一个一个往后选择数字,不断向前进行插入
直接插入排序
时间复杂度:
最好情况:全部有序,此时只需遍历一次,最好的时间复杂度为O ( n )
最坏情况:全部反序,内层每次遍历已排序部分,最坏时间复杂度为 O(N^2)
综上,因此直接插入排序的平均时间复杂度为O(N^2)
空间复杂度:
没有使用多余的空间,是在原数组上进行操作,所以时间复杂度为O(1)
稳定性:
稳定的,相同大小的数字前后位置不变
思路举例:
代码详细实现:
public void sort(int[] arr) {
if (arr == null || arr.length == 0) return;
//由于起始的时候,第一个数已经排好,从后面开始插入即可,所以这个循环从1开始即可
for (int i = 1; i < arr.length; i++) {
int temp = arr[i];//暂时存放需要插入的数
int j;
//由于需要与前面排好序的进行比较 且 从后往前比较,所以j从i-1开始到0结束
for (j = i - 1; j >= 0; j--) {
if (arr[j] > temp) {
//如果前面的数大于temp 则这个前面的数向后移动一下
arr[j + 1] = arr[j];
} else {
//出现<=temp的数字直接跳出循环
break;
}
}
//跳出循环之后,把temp放入j+1的位置,temp就插入好了
arr[j + 1] = temp;
}
}
希尔排序
希尔排序其实是直接插入排序的升级版本,不过是把 数组进行分组然后进行插入排序
时间复杂度:大约在O(n ^ 1.25)到O(1.6 * n ^ 1.25)取决于增量gap的值
稳定性:不稳定
空间复杂度:O(1)
适用场景:相比直接插入排序,希尔排序更适合无序的数据,尤其数据量大的时候,能节省很多运行时间
思路举例:选取增量,进行分组排序,其实相比于直接插入排序就是多了一个分组增量,在代码中体现就是多套一个while循环来改变增量的值
代码实现:
public void sort(int[] arr) {
//这里图简单 我们直接使用gap每次/2来确定gap的值
int gap = arr.length;
while (gap != 1) {
gap /= 2;
//下面的步骤其实和 直接插入排序一样 不过是每次增加gap
for (int i = 1; i < arr.length; i += gap) {
int j;
int temp = arr[i];
for (j = i - 1; j >= 0; j--) {
if (arr[j] > temp) {
arr[j + 1] = arr[j - 1];
} else {
return;
}
}
arr[j + 1] = temp;
}
}
}
选择排序
每次从待定元素中,选出最小的那个,然后放在序列的起始位置,继续排序后面的。
直接选择是每个找一遍选择,堆排序是利用大根堆,找最大的放到后面
直接选择排序
时间复杂度:O(N^2)(对数据是否有序不敏感)
空间复杂度:O(1)
稳定性:不稳定过程 每次选出一个最小值放到数据的第一个位置
过程:不断选出最小的,分别放到第一个位置,第二个位置
代码:
//普通版本:找到最小的放到左边
publicvoidselectSort(int[] array) {
for (inti=0; i < array.length; i++) {
intminIndex= i;
for (intj= i + 1; j < array.length; j++) {
if (array[j] < array[minIndex])
{ minIndex = j;}
}
swap(array, minIndex, i);
}
}
//升级优化版本:左右两边同时进行,同时找出最大和最小的
publicvoidselectSortPro(int[] array) {
int left, right;
for (left = 0, right = array.length - 1; left < right; left++, right--) {
intminIndex= left;
intmaxIndex= right;
for (intj= left ; j <= right; j++) {
if (array[j] < array[minIndex]) { minIndex = j;}
elseif (array[j] > array[maxIndex]) { maxIndex = j;}
}
swap(array, left, minIndex);
//如果left位置存放的是最大值,则下一步maxIndex的内容被掉包了,要有个if判断一下if (left == maxIndex) { maxIndex = minIndex; }
swap(array, right, maxIndex);
}
}
堆排序
是直接选择排序的优化,相较于直接选择排序, 通过堆的方式 选择 最小值放在前面
时间复杂度:O(N*logN)(大/小根堆调整一次的时间复杂度是 logN)
空间复杂度: O (1)
稳定性: 不稳定
思路过程:加入堆的数据结构,升序使用大根堆,降序使用小根堆
代码:
public void heapSort(int[] arr) {
createdHeap(arr);//把原来的函数变成大根堆
for (int i= arr.length - 1; i > 0; i--) {
swap(arr, 0, i);//大根堆 arr[0]最大 把他挪到最后面
shiftDown(arr, 0, i);//再继续对 arr
}
}
//建立大根树的方法
private void createdHeap(int[] arr) {
int len= arr.length;
for (int i= len - 1; i >= 0; i--) { shiftDown(arr, i, len); }
}
//向下寻找 ---- 注意这个代码一定要立马能写出来
private void shiftDown(int[] arr, int parent, int len) {//能够操作的界限是 parent 到 len-1
int son= parent * 2 + 1;//左孩子的位置
while (son < len) {
if (son+1 < len && arr[son] < arr[son+1]){ son++; }//找到左右孩子比较大的那一个大孩子
else {
if (arr[parent] < arr[son]) {//如果大孩子 比 父亲大 那么就交换
swap(arr, parent, son);
parent = son;
son = parent * 2 + 1;
} else { break; }
}
}
}
交换排序
选出一个较大的数和一个较小的数,交换位置。将小数放前面,大数放到后面。
冒泡排序
是一个很简单的排序,编写最简单代码且不要求什么的时候可以使用。
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:稳定
思路:每次交换把大的数都换到后面。每个循环能把最大的数,交换到最后面的位置。
代码:
public void bubbleSort(int[] arr) {
//记住两个for的结束条件
for (int i=0; i < arr.length - 1; i++) {
for (int j=0; j < arr.length - 1 - i; j++) {
if (arr[j]>arr[j+1]){ swap(arr,j,j+1); }
}
}
}`
快速排序
时间复杂度:O(N*logN)
空间复杂度:O(log2n)~O(n) ( 有序数据 的空间复杂度是O(N))
稳定性:不稳定
适用场景:相较于希尔排序,更适合无序,因为是递归有序的情况可能造成堆满
方法:递归,基本写法和树类似
先写一个sort函数,脱裤子放屁,让使用者只传入一个数组即可
写quicklySort函数,参数有 arr,left,right,负责递归,只要right还小于left就不断进行递归执行。
写核心代码 参数依旧是arr,left,right,但返回值为root,负责排序实现。具体的实现方法有下面三种,Hoare版本,挖坑法(个人认为最简答的),双指针法。
代码:
Hoare版
任意选取待排序序列中的一个元素作为基准值,按照该排序码将待排序集合分割成两个子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后再对左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
//脱裤子放屁,让使用者只需要传入一个数组就可以排序
public void quicklySort(int[] arr) {
quicklySort(arr, 0, arr.length - 1);//传入数组和数组的左右边界
}
private void quicklySort(int[] arr, int left, int right) {
if (left >= right) return;
int root = hoare(arr, left, right);
quicklySort(arr, root + 1, right);//操作左子序列
quicklySort(arr, left, root - 1);
}
private int hoare(int[] arr, int left, int right) {
int root = left;
while (right > left) {
//为啥要取等号?不取就死循环了,一直交换
while (right > left && arr[right] >= arr[root]) {
right--;
}
while (right > left && arr[left] <= arr[root]) {
left++;
}
swap(arr, left, right);//这里使用交换,跟挖坑法有所区别
}
swap(arr, root, left);
return left;
}
挖坑法
挖坑法大体思路与hoare法思路相同
基本思想是:将基准值保存到标记位中,这样最右侧位置就形成了一个坑位,然后左侧标注位往右遍历找比基准值大的元素,找到后将该元素填入右侧坑位中,该位置就又形成了一个新的坑位,坑位不断左右变换,重复上述过程直到左右标志位相遇,最后将基准值放入最后的坑位中,最对左右子序列重复上述过程直到整个序列排序完成。
public void digQuicklySort(int[] arr){
digQuicklySort(arr,0,arr.length-1);
}
private int digQuicklySort(int[] arr,int left,int right){
int root = dig(arr,left,right);
digQuickluSort(arr,root+1,right);
digQuickluSort(arr,left,root+1);
return root;
}
private int dig(int[] arr,int left,int right){
int root= arr[left];//key存放基
while(left<right){//注意比较挖坑法与Hoare的区别
while(left<right && arr[right]>=root){ right--; }
arr[left] = arr[right];//这时候右边较大值,放到了左边,而坑到了右边
while(left<right && arr[left]<= root){ left++; }
arr[right] = arr[left];//这时候左边较小值又把右边的坑填了,而左边有了坑
}
arr[left] = root;//把root基 补到中间的坑
return left;
}
面试:手写快排.....如何能优化一下?.....
快速排序优化
优化1.中位数做基
问题:快速排序不适合基本有序数据
原因:当元素有序的时候,会一直递归,递归的深度过深,使递归速度较慢。
解决方式:选取一个中位数作为 基,下面以挖坑法为例:
public void digQuicklySort(int[] arr){
digQuicklySort(arr,0,arr.length-1);
}
privateintdigQuicklySort(int[] arr,int left,int right){
int root= findMid(arr, left, right);//找到中位数
swap(arr, root, left);//将这三个数 的中位数 ,放到最左边,作为基使用。
root = dig(arr,left,right);
digQuickluSort(arr,root+1,right);
digQuickluSort(arr,left,root+1);
return root;
}
//findMid并不是找这个数组的中位数,而是选择 left right left+right/2 这三个位置中间大小的数
private int findMid(int[] arr, int left, int right) {
int mid= (left + right) / 2;
//寻找左边为 left mid right 这三个数的下标
if (arr[right] > arr[left]) {
if (arr[mid] > arr[right]) return right;
else if (arr[mid] < arr[left]) return left;
else return mid;
} else {
if (arr[mid] > arr[left]) return left;
else if (arr[mid] < arr[right]) return right;
else return mid;
}
}
优化2.减少递归
问题:递归过多会造成栈满
解决思路:叶子很多,假如到最后几层我们是不是就可以直接使用 插入排序,而且最后的元素也刚好基本趋于稳定
解决方式 :加一个if语句判断是继续递归还是直接插入排序,下面以挖坑法为例:
非递归实现快排
方法: 使用栈,方法基本参考非递归解决二叉树的问题
速度: 不用很多优化,但代码相对不好想。
优化??:可以尝试加入三数取中,插入排序的优化,参考3.1 3.2
代码:
public void quicklySortNoRecursion(int[] arr){
if (arr.length<=1) return;
Stack<Integer> stack = newStack<>();
int left=0,right = arr.length-1;
stack.push(left);
stack.push(right);
while(!stack.isEmpty()){
right = stack.pop();//记得一定要先弹出right 因为right是后放进去的
left = stack.pop();
int root= quicklyDig(arr,left,right);
if (root>left+1){//这个代表,左边至少有俩元素
stack.push(left);//注意这里也要放做再放右
stack.push(root-1);
}
if (root<right-1){//这个代表右边至少有两个元素
stack.push(root+1);
stack.push(right);
}
}
}
}
归并排序
基本思想:先分解 后合并
常用场景:排序的数据量过大,内部排序的空间无法满足,这个时候就要使用外部排序。而归并排序就是最常用的外部排序
例子: 内存只有1G 但是需要排序的数据有100G
先把文件分成200份,每个512M,分别对512M排序,内存已经可以放下,任意排序都可以使用,进行2路归并,同时对200分有序文件做归并处理,最后结果就有序了
代码:
递归实现
public class MergeSort {
publicvoidmergeSort(int[] arr) {
mergeSort(arr, 0, arr.length - 1);
}
private void mergeSort(int[] arr, int left, int right) {
if (right <= left) return;
int mid= (right + left) / 2;
mergeSort(arr, left, mid);//分解左边
mergeSort(arr, mid + 1, right);//分解右边
merge(arr, left, right, mid);//合并
}
private void merge(int[] arr, int left, int right, int mid) {
int left1= left, right1 = mid;
int left2= mid + 1, right2 = right;
int[] arr2 = newint[right - left + 1];
intarr2Index=0;//arr2的坐标两个归并段 都有数据,那边的数据小那边先放
while (right1 >= left1 && right2 >= left2) {
if (arr[left1] <= arr[left2]) arr2[arr2Index++] = arr[left1++];
else arr2[arr2Index++] = arr[left2++];
}
//当走到这里的时候 说明 有个归并段 当中 没有了数据 ,拷贝另一半的全部 到tmpArr数组当中
while (right1 >= left1) arr2[arr2Index++] = arr[left1++];
while (right2 >= left2) arr2[arr2Index++] = arr[left2++];
for (inti= left; i <= right; i++) {
arr[i] = arr2[i - left];
}
}
}
非递归实现
public void mergerNoSort(int[] arr) {
if (arr.length <= 1) return;
//假设每个元素都是一组数据intgap=1;//表示每组元素的个数
while (gap <= arr.length) {
for (inti=0; i < arr.length; i += 2 * gap) {
int left= i;
int right= i + 2 * gap - 1;
if (right > arr.length - 1) { right = arr.length - 1; }
int mid= left + gap-1;
if (mid>=arr.length) { mid = arr.length-1; }
merge(arr, i, right, mid);
}
gap *= 2;
}
}