二分插入排序
public static void insertSort(int[] array) {
// 从倒数第二位开始,遍历到底0位,遍历 N-1 次
for (int i = array.length - 2; i >= 0; i--) {
// 存储当前抽离的元素
int temp = array[i];
int index = searchIndex(array, i + 1, array.length - 1, temp);
// #1. 根据插入的索引位置,进行数组的移动和插入
int j = i + 1;
while (j <= index) {
array[j - 1] = array[j];
j++;
}
array[j - 1] = temp;
}
}
冒泡排序
四点核心思想
指向数组两个相邻元素(最初是数字头两个元素),并比较大小。
如果前比后大,则交换位置。
后比前大,不交换。
依次向后移动,每次循环将最大元素移动至最后一个位置(比较1、2,2、3...)。
// 冒泡排序
public static void bubbleSort(int[] array) {
// 1. 每次循环,都能冒泡出剩余元素中最大的元素,因此需要循环 array.length 次
for (int i = 0; i < array.length; i++) {
// 2. 每次遍历,只需要遍历 0 到 array.length - i - 1中元素,因此之后的元素都已经是最大的了
for (int j = 0; j < array.length - i - 1; j++) {
//3. 交换元素
if (array[j] > array[j + 1]) {
int temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
}
}
}
}
选择排序
四点核心思想
利用两个变量,一个储存当前最大值,一个储存当前最大值所在的索引。
一次比较后面的元素,如果发现比当前最大值大,则更新最大值,并且更新索引。
直到遍历结束,将最大值放在数组最右边,也就是交换最右边元素和当前最大值。
重复以上。
冒泡排序和选择排序时间复杂度都是O(N^2)但选择排序比冒泡快一倍左右
public void selectSort(int[] arr){
//1.每次循环都将最大值排到数组最右边,循环array.length-1次
for(int i=0;i<arr.length-1;i++){
int ptr = 0;//取数组第一个元素
for(int j = 1;j<arr.length-i;j++){
//用第一个元素与后面元素依次比较
if(arr[j]>arr[ptr]){
ptr = j;
}
}
int temp = arr[arr.length-i-1];
arr[arr.length-i-1] = arr[ptr];
arr[ptr] = temp;
}
}
插入排序
核心规则四点
第一轮,抽离数组末尾倒数第二个元素,作为临时元素。
用临时元素与数组后面的元素进行比较:如果_后面的元素_值小于临时元素,则后面的元素左移。
如果后面的元素大于临时元素,或者已经移动到数组末尾,则将临时元素插入当前空隙中。
重复上面步骤,完成排序。
核心为通过每次迭代,将部分元素排序,以达到最终全部排序的效果。
第一次迭代
第二次迭代
第三次迭代
每次迭代多讲尾部部分数字排好序
最差情况时间复杂度O((N^2+N)/2)
忽略常数O(N^2)
public void insertSort(int[] arr){
for(int i=0;i<arr.length-1;i++){
int ptr=arr.length-i-2;//取未排序数组的倒数第二个元素
for(int j=ptr;j<arr.length-1;j++){
if(arr[j]>arr[j+1]){
int temp = arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
二分插入排序
将插入排序和二分查找法结合在一起可以进一步提高排序效率
二分插入法
在原来基础上,核心加入二分查找位置的函数
// 查找应该插入的索引位置
public static int searchIndex(int[] array, int left, int right, int aim) {
// 循环查找节点位置
while (left < right) {
int middle = (left + right) / 2;//middle取数组中间的数,注意l+r为奇数情况
int value = array[middle];
if (value < aim) {
left = middle + 1;
} else {
right = middle - 1;
}
}
// #1. 如果最终元素仍然大于目标元素,则将索引位置往左边移动一个
if(array[left] > aim){
return left -1;
}
// 否则就是当前位置
return left;
}
// 查找应该插入的索引位置
public static int searchIndex(int[] array, int left, int right, int aim) {
// 循环查找节点位置
while (left < right) {
int middle = (left + right) / 2;//middle取数组中间的数,注意l+r为奇数情况
int value = array[middle];
if (value < aim) {
left = middle + 1;
} else {
right = middle - 1;
}
}
// #1. 如果最终元素仍然小于目标元素,则将索引位置往右边移动一个
if(array[left] < aim){
return left + 1;
}
// 否则就是当前位置
return left;
}
二分插入排序代码
// 插入排序
public static void insertSort(int[] array) {
// 从倒数第二位开始,遍历到底0位,遍历 N-1 次
for (int i = array.length - 2; i >= 0; i--) {
// 存储当前抽离的元素
int temp = array[i];
int index = searchIndex(array, i + 1, array.length - 1, temp);
// #1. 根据插入的索引位置,进行数组的移动和插入
int j = i + 1;
while (j <= index) {
array[j - 1] = array[j];
j++;
}
array[j - 1] = temp;
}
}
归并排序
递归思想
基准条件(递归结束条件)
递归公式(考虑递归如何执行下去)
递归举例,斐波那契数列n=(n-1)+(n-2)
public int fibo(int n){
if(n<0){
return 0;
}
if(n==0){
return 0;
}
if(n==1||n==2){
return 1;
}else {
return fibo(n-1)+fibo(n-2);
}
}
分治思想
分治思想是通过递归,将原问题分成几个规模较小但是类似于原问题的子问题,通过递归方式来求解这些小问题,最后将子问题合并来得到原问题的解。
归并排序核心思路:
将大数组分为小数组,将每个小数组排好列,再将小数组组合成大数组
拆分过程中核心逻辑
如何递归进行数组拆分
如何将原数组拆分为两个子数组
首先创建两个函数
// 归并排序,返回排好序的数组
public static int[] mergeSort(int[] array) {
}
// 拷贝原数组的部分内容,从 left 到 right
public static int[] subArray(int[] source, int left, int right) {
}
然后完成第二个函数
// 拷贝原数组的部分内容,从 left 到 right
public static int[] subArray(int[] source, int left, int right) {
// 创建一个新数组
int[] result = new int[right - left];//新数组长度为right-left,从索引l到索引r-1
// 依次赋值进去
for (int i = left; i < right; i++) {
result[i - left] = source[i];
}
return result;
}
难点mergeSort类,如何实现递归拆分数组先找基准条件:数组元素只有一个,则直接返回
// 归并排序,返回排好序的数组
public static int[] mergeSort(int[] array) {
if(array.length == 1){
return array;
}
}
如果数组元素不止一个,则将数组从中间进行拆分,分别调用mergSort
// 归并排序,返回排好序的数组
public static int[] mergeSort(int[] array) {
// 为了方便查看结果,我们将每个数组进行打印
System.out.println(Arrays.toString(array));
if (array.length == 1) {
return array;
}
int middle = array.length / 2;
// #1. 处理 0 到 middle 左侧数组部分(不包括middle)
int[] left = mergeSort(subArray(array, 0, middle));
// #2. 处理 middle 到 array.length 右侧数组部分(不包括索引array.length)
int[] right = mergeSort(subArray(array, middle, array.length));
// TODO处理合并问题
return array;
}
治---如何进行合并排序
观察可知,首先我们需要观察两个需要被排序的数组,数组内部已经被排好序
所以现在应进行两个有序数组的合并排序
将两个数组的数组指针都指向数组头,比较大小,将小的值存入新数组,指针后移,指向较大值的指针不变,以此类推
时间复杂度
两个数组只遍历一次,复杂度为O(log(N))
// 归并排序
public static int[] mergeSort(int[] array) {
// 为了方便查看结果,我们将每个数组进行打印
if (array.length == 1) {
return array;
}
int middle = array.length / 2;
// 处理 0 到 middle 左侧数组部分
int[] left = mergeSort(subArray(array, 0, middle));
// 处理 middle 到 array.length 右侧数组部分
int[] right = mergeSort(subArray(array, middle, array.length));
// TODO处理合并问题->两个有序数组合并为一个有序数组
int l = 0;
int r = 0;
int index = 0;
// 依次比较左右两个数组
while (l < left.length && r < right.length) {
//当其中一个达到数组末尾时跳出
array[index] = Math.min(left[l], right[r]);
index++;
if (left[l] < right[r]) {
l++;
} else {
r++;
}
}
// 右侧数组已经遍历完成,左侧有剩余
if (l < left.length) {
for(int i = l; i < left.length; i++){
array[index] = left[i];
index++;
}
}
// 左侧数组已经遍历完成,右侧有剩余
if(r < right.length){
for(int i = r; i < right.length; i++){
array[index] = right[i];
index++;
}
}
return array;
}
// 拷贝原数组的部分内容,从 left 到 right
public static int[] subArray(int[] source, int left, int right) {
// 创建一个新数组
int[] result = new int[right - left];
// 依次赋值进去
for (int i = left; i < right; i++) {
result[i - left] = source[i];
}
return result;
}
重点快速排序
(暂未完善代码后续抽时间完善)
一、取一个数作为基准数(_为了方便,取数组第一个元素)
二、取数组的最左边数组指针 i 以及最右边数组指针 j _
三、先让右边的指针 j 从右向左移动,并且对比每个元素于基数的大小,若某元素比基数小,则j停止移动.
四、移动最左边指针并且当遇到元素比基数大时停止
五、交换j与i指向的值(实现左边为比基数小的数,右边为比基数大的数)
六、重复3-5直到i与j重合,再将基数与i指向的值交换,此时基数左边的全比基数小,右边全比基数大
七、然后再分别将左,右无序数组再按以上方法进行排序
最后,此文章为学习过程中的笔记或自己的理解,学习资料来源为 优课达 以及 csdn 博主文章。