笔记依照[尚硅谷·数据结构]
排序算法
内部排序
- 插入排序
- 直接插入排序
- 希尔排序
- 选择排序
- 简单选择排序
- 堆排序
- 交换排序
- 冒泡排序
- 快速排序
- 归并排序
- 基数排序
交换排序
冒泡排序
排序思想 : 对待排序序列,从前向后,依次比较相邻元素的值,如果发现逆序就交换.
public static void bubbleSort(int[] arr) {
//冒泡排序优化: 如果在某一次的排序中没有发生一次转换 ,则证明这个序列已经是有序的了
int temp_III = 0;
Boolean flagII = false; // 是否交换
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]) {
flagII = true;
temp_III = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp_III;
}
}
System.out.println(Arrays.toString(arr));
if (flagII == false) {
break;
} else {
flagII = false;
}
}
}
- 易错分析
- 只要这一轮的排序中有一个没有交换位置 就把flag置位false 就跳出循环 这显然就是错的 . 我们要的结果是 在这一轮置换中,只要发生了一个置换操作,就说明这个序列还不是有序的, 循环要继续运行
public static void bubbleSort(int[] arr) {
//冒泡排序优化: 如果在某一次的排序中没有发生一次转换 ,则证明这个序列已经是有序的了
int temp_II = 0;
Boolean flag = true; // 是否交换
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]) {
temp_II = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp_II;
} else {
flag = false;
/*
这里这么写的话 意思就是: 只要这一轮的排序中有一个没有交换位置 就把flag置位false 就跳出循环 这显然就是错 的 . 我们要的结果是 在这一轮置换中,只要发生了一个置换操作,就说明这个序列还不是有序的, 循环要继续运行
*/
}
}
System.out.println(Arrays.toString(arr));
if (flag == false) {
break;
}
}
}
快速排序
快速排序是冒泡排序的一种改进
排序思想:
- 通过一趟排序将待排数据分为两个独立的部分, 其中的一部分的数据比另一部分的所有数据都要小,
- 然后重复上面的额步骤对两个部分在排序 ,
- 整个过程可以递归 ,
- 最后就是得到的就是一个有序的数列.
package sort;
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] arr = {-9, 78, 0, 23, -567, 0};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
// l p r
public static void quickSort(int[] arr, int left, int right) { // |----------0----------|
int l = left;
int r = right;
int temp = 0;
int pivot = arr[(left + right) / 2]; // pivot 中轴值
// 这个while循环的目的是: 比pivot小的值放在它的左边 ,比pivot大的值放在它的右边
while (l < r) {
while (l < r && arr[l] < pivot) { //在数据的左边一直找到一个比pivot大于等于的数字才结束
l++;
}
while (l < r && arr[r] > pivot) { //在数据的左边一直找到一个比pivot小于等于的数字才结束
r--;
}
//上面两个循环出来了之后, arr[l]就是大于pivot的数字,arr[r]就是小于pivot的数字, 但是这里并不知道l是否已经跑到了右边, r是否跑到的左边. 所以下面判断一下
if (l >= r) { //如果这个条件成立, 说明pivot左右两边的值, 已经是按照要求分好的小于大于等于pivot的值
break;
}
//交换 两边错误的量
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
// 如果交换完成之后, 发现arr[l] == pivot相等 r前移
if (arr[l] == pivot) {
r--;
}
// 如果交换完成之后, 发现arr[r] == pivot相等 l后移
if (arr[r] == pivot) {
l++;
}
}
//如果l = r 必须l++ , r-- 否则会出现栈溢出
if (l == r) {
l++;
r--;
}
//向左递归
if (left < r) {
quickSort(arr, left, r);
}
if (right > l) {
quickSort(arr, l, right);
}
}
}
选择排序
排序思想 : 第一轮从整个数组里面选取一个最小的数 , 然后和arr[0]进行交换 .
第二轮从整个数组里面选取一个最小的数 , 这时不包括arr[0] , 然后和arr[1]进行交换 .
…
第N轮 从 …
package sort;
import java.util.Arrays;
public class SelectSort {
public static void main(String[] args) {
int[] arr = {12, 6, 7, 3, 9};
selectSort(arr);
}
public static void selectSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int min = arr[i], arrIndex = i;
for (int j = arr.length - 1; j > i; j--) {
//int j = i + 1; j < arr.length - 1; j++
if (min > arr[j]) {
min = arr[j];
arrIndex = j;
}
}
if (arrIndex != i) {
arr[arrIndex] = arr[i];
arr[i] = min;
}
System.out.println("在第" + (i + 1) + "轮之后" + Arrays.toString(arr));
}
}
}
插入排序
简单插入排序
排序思想
把n个待排序的的元素看成为一个有序表和一个无序表 , 开始的时候有序表只包含一个元素,无序表中有n-1个元素,排序的过程中每次从后面的无序表中取出第一个元素,然后和有序表中进行比较,从而找到自己的位置.
package sort;
import java.util.Arrays;
public class InsertionSort {
public static void main(String[] args) {
int[] arr = {101, 34, 119, 1};
insertSort(arr);
}
public static void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int insertVal = arr[i];
int preIndex = i - 1; //取得插入数据的前一个数字的下标
while (preIndex >= 0 && insertVal < arr[preIndex]) {
arr[preIndex + 1] = arr[preIndex]; //将较大的数字后移
preIndex--;
}
arr[preIndex + 1] = insertVal;
System.out.println(Arrays.toString(arr));
}
}
}
希尔排序
方法一: 交换法
排序思路: 先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
package sort;
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
shellSort(arr);
}
//希尔排序:交换法
public static void shellSort(int[] arr) {
for (int gap = arr.length / 2; gap > 0; gap /= 2) { // 进行分组
for (int i = gap; i < arr.length; i++) {// 循环的数 = 要进行的分组数,也就是多少个分组就循环多少次
for (int j = i - gap; j >= 0; j -= gap) { // 分组中进行交换
if (arr[j] > arr[j + gap]) {
int temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
}
方法二: 位移法
//希尔排序: 移位法
public static void shellSortMoveSite(int[] arr) {
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
int min = arr[i];
int index = i;
while (index - gap >= 0 && min < arr[index - gap]){
arr[index] = arr[index - gap];
index -= gap;
}
arr[index] = min;
}
System.out.println(Arrays.toString(arr));
}
}
归并排序
算法描述: 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并
排序思路:
package sort;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
int[] temp = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, temp);
System.out.println(Arrays.toString(arr));
}
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid + 1, right, temp);
merge(arr, left, mid, right, temp);
}
}
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left;
int j = mid + 1;
int t = 0;
while (i <= mid && j <= right) {
if (arr[i] >= arr[j]) {
temp[t] = arr[j];
t += 1;
j += 1;
} else {
temp[t] = arr[i];
t += 1;
i += 1;
}
}
while (i <= mid) {
temp[t] = arr[i];
t += 1;
i += 1;
}
while (j <= right) {
temp[t] = arr[j];
t += 1;
j += 1;
}
t = 0;
//int tempLeft = left;
while (left <= right) {
arr[left] = temp[t];
t += 1;
left += 1;
}
}
}
基数排序
排序思想:
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
排序思路:
package sort;
import java.util.Arrays;
public class RadixSort {
public static void main(String[] args) {
int[] arr = {53, 3, 542, 748, 14, 214};
radixSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void radixSort(int[] arr) {
int max = arr[0], maxDigit;
for (int i = 1; i < arr.length; i++) {
if (max <= arr[i]) {
max = arr[i];
}
}
maxDigit = (max + "").length();
for (int i = 0, n = 1; i < maxDigit; i++, n *= 10) {
int[][] bucket = new int[10][arr.length];
int[] bucketElCounts = new int[10];
for (int j = 0; j <= arr.length - 1; j++) {
int DigitNum = arr[j] / n % 10;
bucket[DigitNum][bucketElCounts[DigitNum]] = arr[j];
bucketElCounts[DigitNum]++;
}
int index = 0;
for (int j = 0; j < bucketElCounts.length; j++) {
if (bucketElCounts[j] != 0) {
for (int k = 0; k < bucketElCounts[j]; k++) {
arr[index++] = bucket[j][k];
}
}
bucketElCounts[j] = 0;
}
}
}
}
查找算法
二分查找
基本思想:
也称为是折半查找,属于有序查找算法。用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点
要求:
有序数组
package search;
import java.util.ArrayList;
public class BinarySearch {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 7, 8};
ArrayList<Integer> integers = binarySearchII(arr, 0, arr.length - 1, 7);
System.out.println(integers);
}
public static ArrayList<Integer> binarySearchII(int[] arr, int left, int right, int findVal) {
ArrayList<Integer> midNo = new ArrayList<>();
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) {
return binarySearchII(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binarySearchII(arr, left, mid - 1, findVal);
} else {
int temp = mid - 1;
while (true) {
if (temp < 0 || arr[temp] > findVal) {
break;
}
midNo.add(temp);
temp++;
}
temp = mid + 1;
while (true) {
if (temp > arr.length - 1 || arr[temp] != findVal) {
break;
}
midNo.add(temp);
temp--;
}
return midNo;
}
}
}
插值查找
首先考虑一个新问题,为什么上述算法一定要是折半,而不是折四分之一或者折更多呢?
可以将查找的点改进为如: mid=low+(key-a[low])/(a[high]-a[low])*(high-low), 也就是将上述的比例参数1/2改进为自适应的
package search;
public class InsertSearch {
public static void main(String[] args) {
int[] arr = new int[100];
for (int i = 0; i < 100; i++) {
arr[i] = i + 1;
}
System.out.println(insertSearch(arr, 0, arr.length - 1, 100));
}
/*
插值查找算法也要求数据有序
*/
public static int insertSearch(int[] arr, int left, int right, int findVal) {
if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
return -1;
}
int mid = left + (right - left) * ((findVal - arr[left]) / (arr[right] - arr[left]));
int midVal = arr[mid];
if (findVal > midVal) { // 应该向右边递归查找
return insertSearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return insertSearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
}
斐波那契查找
原理:
斐波那契查找的原理和上面两个类似, 仅仅改变了mid的位置, mid不再是中间值或者是插值得到, 而是位于黄金分割点附近
[ mid = low + F(k -1) - 1 ]
package search;
import java.util.Arrays;
public class FibonacciSearch {
public static int maxSize = 20;
public static void main(String[] args) {
int[] arr = new int[]{1, 8, 10, 89, 1000, 1234};
System.out.println(fibSearch(arr, 1234));
}
// 先获取一个斐波那契数列 (非递归的方式)
public static int[] fib() {
int[] f = new int[maxSize];
f[0] = 1;
f[1] = 1;
for (int i = 2; i < maxSize; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f;
}
/**
* 使用非递归的方法编写代码
*
* @param arr
* @param key 需要查找的值
* @return 返回对应的下标
*/
public static int fibSearch(int[] arr, int key) {
int low = 0;
int high = arr.length - 1;
int k = 0; //斐波那契分割数值的下标
int mid = 0;
int f[] = fib();
//获取斐波那契分割数值的下标
while (high > f[k] - 1) {
k++;
}
//因为f[k]值可能大于arr的长度, 所以需要使用一个Arrays类, 构造一个新的数组, 并指向temp[], 不足的会用0进行填充
int[] temp = Arrays.copyOf(arr, f[k]);
//实际上需求使用a数组最后的数字填充temp
for (int i = high + 1; i < temp.length; i++) {
temp[i] = arr[high];
}
//使用循环找到数字key
while (low <= high) {
mid = low + f[k - 1] - 1;
if (key < temp[mid]) { //向左边查找
high = mid - 1;
k--;
} else if (key > temp[mid]) {
low = mid + 1;
k -= 2;
} else {
if (mid <= high) {
return mid;
} else {
return high;
}
}
}
return -1;
}
}