一、八大排序
排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程。
排序分类
总体对比
1 冒泡排序
基本思想
-
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
-
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,在进行)
代码实现:
public static void bubbleSort(int[] arr){
int temp;
boolean flag = 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]){
flag = true;
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
if (!flag) { // 在一趟排序中,一次交换都没有发生过
break;
} else {
flag = false; // 重置flag!!!, 进行下次判断
}
}
}
2 选择排序
基本思想
- 选择排序(select sorting)也是一种简单的排序方法。
- 它的基本思想是:第一次从arr[0]~arr[n-1] 中选取最小值,与arr[0]交换,
- 第二次从arr[1]~ arr[n-1]中选取最小值,与arr[1]交换,
- 第三次从arr[2]~ arr[n-1]中选取最小值,与arr[2]交换,…,
- 第i次从arr[i-1]~ arr[n-1]中选取最小值,与arr[i-1]交换,…,
- 第n-1次从arr[n-2]~arr[n-1]中选取最小值,与arr[n-2]交换,
总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
代码实现:
public static void selectSort(int[] arr){
int temp;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i+1; j < arr.length; j++) {
if (arr[i]>arr[j]){
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
3 插入排序
基本思想:
- 插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
代码实现:
//插入排序
public static void insertSort(int[] arr){
int temp;
for (int i = 1; i < arr.length; i++) {
// for (int j = i; j >0; j--) {
// if (arr[j]<arr[j-1]){
// temp = arr[j-1];
// arr[j-1] = arr[j];
// arr[j] = temp;
// }
// 优化
int j =i;
while (j>0 && (arr[j]<arr[j-1])){
temp = arr[j-1];
arr[j-1] = arr[j];
arr[j] = temp;
j--;
}
}
}
4 希尔排序
基本思想
- 希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
代码实现
// 优化
public static void shellSort2(int[] arr){
int temp;
for (int i = arr.length/2; i >0 ; i/=2) {// 希尔排序的灵魂
for (int j = i; j <arr.length ; j++) {// 下面类似如插入排序
int k = j-i;
while (k>=0&&(arr[k]>arr[k+i])){ //这里注意是k和k+i
temp = arr[k];
arr[k] = arr[k+i];
arr[k+i] = temp;
k -=i;
}
}
}
}
5 快速排序
基本思想
- 快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
代码实现
public static void quickSort2(int[] arr,int left, int right){
if (left>right){ // 结束条件
return;
}
int low =left;
int high = right;
int base = arr[left]; //第一个位基准位
int temp;
while (low <high){
while (arr[high]>=base && low<high){///找到小于基准位的数
high--;
}
while (arr[low]<=base && low<high){// 找到大于基准位的数
low++;
}
if (low<high){ // 交换
temp = arr[high];
arr[high] = arr[low];
arr[low] = temp;
}
}
arr[left] = arr[low]; // 交换基准位和中间值
arr[low] = base;
quickSort2(arr,left,low-1); //左边递归
quickSort2(arr,low+1,right); // 右边递归
}
6 归并排序
基本思想
- 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
代码实现
//分+合方法
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);
}
}
//合并的方法
/**
*
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param 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;
//(一)
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while (i<=mid && j<=right){
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到 temp数组
//然后 t++, i++
if (arr[i]<=arr[j]){
temp[t] = arr[i];
i++;
t++;
}else{//反之,将右边有序序列的当前元素,填充到temp数组
temp[t] = arr[j];
j++;
t++;
}
}
//(二)
//把有剩余数据的一边的数据依次全部填充到temp
while (i<=mid){//左边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[i];
i++;
t++;
}
while (j<=right){//右边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[j];
j++;
t++;
}
//(三)
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
int tempLeft = left;
t = 0;
while (tempLeft<=right){
arr[tempLeft] = temp[t];
tempLeft++;
t++;
}
}
}
7 基数排序
基本思想
- 将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
代码实现
//基数排序方法
public static void radixSort(int[] arr){
// 找出最大数
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i]>max){
max = arr[i];
}
}
//最大数的长度
int maxLength = (max+"").length();
// 定义桶
int[][] bucket = new int[10][arr.length];
// 定义每个桶的中数据多少,用于取数据。
int[] bucketElementCounts = new int[10];
//
for (int i = 0,n=1; i <maxLength ; i++,n*=10) {
// 数据放入桶中
for (int j = 0; j < arr.length; j++) {
//
int digitOfElement = arr[j]/n%10;
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
// 从桶中取数据
int index = 0;
for (int k = 0; k < bucketElementCounts.length; k++) {
if (bucketElementCounts[k]!=0){
for (int l = 0; l <bucketElementCounts[k] ; l++) {
arr[index] = bucket[k][l];
index++;
}
}
bucketElementCounts[k] = 0;
}
}
}
8 堆排序
基本思想:
- 将待排序序列构造成一个大顶堆
此时,整个序列的最大值就是堆顶的根节点。
将其与末尾元素进行交换,此时末尾就为最大值。
然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
代码实现:
//编写一个堆排序的方法
public static void heapSort(int arr[]) {
int temp = 0;
System.out.println("堆排序!!");
// //分步完成
// adjustHeap(arr, 1, arr.length);
// System.out.println("第一次" + Arrays.toString(arr)); // 4, 9, 8, 5, 6
//
// adjustHeap(arr, 0, arr.length);
// System.out.println("第2次" + Arrays.toString(arr)); // 9,6,8,5,4
//完成我们最终代码
//将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆
for(int i = arr.length / 2 -1; i >=0; i--) {
adjustHeap(arr, i, arr.length);
}
/*
* 2).将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
3).重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
*/
for(int j = arr.length-1;j >0; j--) {
//交换
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
adjustHeap(arr, 0, j);
}
//System.out.println("数组=" + Arrays.toString(arr));
}
//将一个数组(二叉树), 调整成一个大顶堆
/**
* 功能: 完成 将 以 i 对应的非叶子结点的树调整成大顶堆
* 举例 int arr[] = {4, 6, 8, 5, 9}; => i = 1 => adjustHeap => 得到 {4, 9, 8, 5, 6}
* 如果我们再次调用 adjustHeap 传入的是 i = 0 => 得到 {4, 9, 8, 5, 6} => {9,6,8,5, 4}
* @param arr 待调整的数组
* @param i 表示非叶子结点在数组中索引
* @param lenght 表示对多少个元素继续调整, length 是在逐渐的减少
*/
public static void adjustHeap(int arr[], int i, int lenght) {
int temp = arr[i];//先取出当前元素的值,保存在临时变量
//开始调整
//说明
//1. k = i * 2 + 1 k 是 i结点的左子结点
for(int k = i * 2 + 1; k < lenght; k = k * 2 + 1) {
if(k+1 < lenght && arr[k] < arr[k+1]) { //说明左子结点的值小于右子结点的值
k++; // k 指向右子结点
}
if(arr[k] > temp) { //如果子结点大于父结点
arr[i] = arr[k]; //把较大的值赋给当前结点
i = k; //!!! i 指向 k,继续循环比较
} else {
break;//!
}
}
//当for 循环结束后,我们已经将以i 为父结点的树的最大值,放在了 最顶(局部)
arr[i] = temp;//将temp值放到调整后的位置
}
二、查找
1 线性查找
public static int seqSearch(int[] arr, int value) {
// 线性查找是逐一比对,发现有相同值,就返回下标
for (int i = 0; i < arr.length; i++) {
if(arr[i] == value) {
return i;
}
}
return -1;
}
2 二分查找
前提条件:数组是排序好的
递归方法:
public static int binarySearch(int[] arr, int left, int right, int findVal){
if (left>right){ //结束条件
return -1;
}
int mid = (left+right)/2; //取中
int midVal = arr[mid];
if (midVal>findVal){ // 左边递归
return binarySearch(arr,left,mid-1,findVal);
}else if (midVal<findVal){ // 右边递归
return binarySearch(arr,mid+1,right,findVal);
}else {
return mid;
}
}
非递归方法
public static int binarySearch2(int[] arr, int findVal){
int head = 0; // 头
int end = arr.length-1;//尾
int mid; //中间
while (head<=end){//结束条件
mid = (head+end)/2; //取中
if (arr[mid] == findVal){//找到
return mid;
}else if(arr[mid]>findVal){//在数组左边
end =mid-1; //另尾等于mid-1
}else {
head = mid+1;//在数组右边,另尾等于mid+1
}
}
return -1;
}
3 插值查找
代码实现
public static int insertValueSearch(int[] arr, int left, int right, int findVal) {
System.out.println("插值查找次数~~");
//注意:findVal < arr[0] 和 findVal > arr[arr.length - 1] 必须需要
//否则我们得到的 mid 可能越界
if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
return -1;
}
// 求出mid, 自适应
int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);//关键所在
int midVal = arr[mid];
if (findVal > midVal) { // 说明应该向右边递归
return insertValueSearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) { // 说明向左递归查找
return insertValueSearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
4 斐波那契(黄金分割法)查找
代码实现
//因为后面我们mid=low+F(k-1)-1,需要使用到斐波那契数列,因此我们需要先获取到一个斐波那契数列
//非递归方法得到一个斐波那契数列
public static int[] fib(int n){
int[] f = new int[n];
f[0] = 1;
f[1] = 1;
for (int i=2;i<20;i++){
f[i] = f[i-1] + f[i-2];
}
return f;
}
//编写斐波那契查找算法
//使用非递归的方式编写算法
/**
*
* @param a 数组
* @param key 我们需要查找的关键码(值)
* @return 返回对应的下标,如果没有-1
*/
public static int fibSearch(int[] a, int key){
int low = 0;
int high = a.length-1;
int k = 0;
int mid;
int[] f = fib(20);
// 获取对应的斐波那契数值
while (high>f[k]-1){
k++;
}
//因为 f[k] 值 可能大于 a 的 长度,因此我们需要使用Arrays类,构造一个新的数组,并指向temp[]
//不足的部分会使用0填充
int[] temp = Arrays.copyOf(a,f[k]);
//实际上需求使用a数组最后的数填充 temp
//举例:
//temp = {1,8, 10, 89, 1000, 1234, 0, 0} => {1,8, 10, 89, 1000, 1234, 1234, 1234,}
for (int i = high+1; i < temp.length; i++) {
temp[i] = a[high];
}
// 查找
while (low<=high){
mid = low+ f[k-1]-1;
if (key<temp[mid]){
high = mid-1;
//为甚是 k--
//说明
//1. 全部元素 = 前面的元素 + 后边元素
//2. f[k] = f[k-1] + f[k-2]
//因为 前面有 f[k-1]个元素,所以可以继续拆分 f[k-1] = f[k-2] + f[k-3]
//即 在 f[k-1] 的前面继续查找 k--
//即下次循环 mid = f[k-1-1]-1
k--;
}else if (key>temp[mid]){
low = mid + 1;
//为什么是k -=2
//说明
//1. 全部元素 = 前面的元素 + 后边元素
//2. f[k] = f[k-1] + f[k-2]
//3. 因为后面我们有f[k-2] 所以可以继续拆分 f[k-1] = f[k-3] + f[k-4]
//4. 即在f[k-2] 的前面进行查找 k -=2
//5. 即下次循环 mid = f[k - 1 - 2] - 1
k-=2;
}else {
if(mid<=high){
return mid;
}else {
return high;
}
}
}
return -1;
}