摘要:
插入排序分為三種:
-
直接插入排序
基本思想就是挨個遍歷,第二個元素和第一個比,第三個元素和前兩個比,第四個和前三個比,如果合適就移動后面的元素,插入當前元素
-
二分插入排序
基於二分查找算法的思想,不挨個比,每次找中間數進行比較,如果合適就移動后面的元素,插入當前元素
-
希爾排序
希爾排序適用大規模且無序的數據,上述兩種適合小規模且有序的數據。
希爾排序的核心思想是分組,根據增量序列分組排序,避免大規模的數據遍歷比較。
交換排序分為兩種:
-
冒泡排序
比較相鄰的元素,如果第一個比第二個大,元素交換
-
快速排序
遞歸思想:取一個基准數進行左右分區,將數組中大於基准數的放在右邊,小於基准數的放在左邊。
對於左右分區,采取相同的策略,選取基准數,分區,直到單個分區中只有一個數。
選擇排序分為兩種:
-
直接選擇排序
選出數組中最小的元素,放在序列起始位置,然后在剩余元素中選取最小的數據,以此類推。
-
堆排序
堆排序核心思想是利用數組的索引映射稱為堆的數據結構(近似二叉樹)。
一,查找算法
常見查找算法分為七種:
-
順序查找
-
二分查找
-
插值查找
-
查找樹
-
分塊查找
-
哈希查找
順序查找
順序查找適合於存儲結構為順序存儲或鏈接存儲的線性表。
從數據結構線形表的一端開始,順序掃描,依次將掃描到的結點關鍵字與給定值k相比較,若相等則表示查找成功;若掃描結束仍沒有找到關鍵字等於k的結點,表示查找失敗。【挨個遍歷】
時間復雜度和空間復雜度都為O(n)。
/**
* 順序查找 45
*/
int x[]=new int[]{1,3,5,6,8,9,45,56,78,86,95};
for (int i =0 ;i<x.length;i++)
{
System.out.println("第"+(i+1)+"次查找");
if(x[i]==45)
{
System.out.println(x[i]+" 查找成功!");
break;
}
System.out.println(x[i]);
}
二分查找
前提:元素必須是有序的。
基本思想:用給定值k先與中間結點的關鍵字比較,中間結點把線形表分成兩個子表,若相等則查找成功;若不相等,再根據k與該中間結點關鍵字的比較結果確定下一步查找哪個子表,這樣遞歸進行,直到查找到或查找結束發現表中沒有這樣的結點。
時間復雜度為O(log2n)
/**
* 二分查找 45
*/
int x[]=new int[]{1,3,5,6,8,9,45,56,78,86,95};
int low = 0;
int high = x.length;
int mid;
int count=1;
while(low<high)
{
mid=(int)(low+high)/2;
System.out.println("第"+count+"次查找"+",比較值為"+x[mid]);
if(x[mid]<45)
{
low=mid+1;
System.out.println("查找結果: 值偏小");
}
else if(x[mid]>45)
{
high=mid-1;
System.out.println("查找結果: 值偏大");
}
else
{
System.out.println("查找成功");
break;
}
count++;
}
插值查找
二分查找的優化版本,將查找點從固定的二分改為自適應,提高查找效率。
mid = low + (key - a[low]) / (a[high] - a[low]) * (high - low)
時間復雜度為O(log2(log2n))
package com.company;
public class Al
{
public static void main(String[] args){
/**
* 插值查找 45
*/
int x[]=new int[]{1,3,5,6,8,9,45,56,78,86,95};
int low = 0;
int high = x.length-1;
int count=1;
while(low<high)
{
int mid = (low + (high-low)*(45-x[low]) / (x[high] - x[low]));
System.out.println("第"+count+"次查找"+",比較值為"+x[mid]);
if(x[mid]<45)
{
low=mid+1;
System.out.println("查找結果: 值偏小");
}
else if(x[mid]>45)
{
high=mid-1;
System.out.println("查找結果: 值偏大");
}
else
{
System.out.println("查找成功");
break;
}
count++;
}
}
}
查找樹
二叉查找樹
- 二叉查找樹定義:
每棵子樹頭節點的值都比各自左子樹上所有節點值要大,也都比各自右子樹上所有節點值要小
二叉查找樹的中序遍歷序列一定是從小到大排列的。
- 查找思路:
假設查找值為x,b為二叉查找樹
若b是空樹,則搜索失敗,否則:
若x等於b的根節點的數據域之值,則查找成功;否則:
若x小於b的根節點的數據域之值,則搜索左子樹;否則:
若x大於b的根節點的數據域之值,則搜索右子樹。
平衡二叉樹
紅黑樹
B樹
B+樹
分塊查找(索引)
分塊查找又稱索引順序查找,它是順序查找的一種改進方法。
算法思想:將n個數據元素”按塊有序”划分為m塊(m ≤ n)。每一塊中的結點不必有序,但塊與塊之間必須”按塊有序”;即第1塊中任一元素的關鍵字都必須小於第2塊中任一元素的關鍵字;而第2塊中任一元素又都必須小於第3塊中的任一元素,……
算法流程:
step1 先選取各塊中的最大關鍵字構成一個索引表;
step2 查找分兩個部分:先對索引表進行二分查找或順序查找,以確定待查記錄在哪一塊中;然后,在已確定的塊中用順序法進行查找。
哈希查找
哈希查找的操作步驟:
-
用給定的哈希函數構造哈希表;
-
根據選擇的沖突處理方法解決地址沖突;
-
在哈希表的基礎上執行哈希查找。
二,排序算法
關於排序算法
相關概念:
正序:關鍵碼從小到大排列
逆序:關鍵碼從大到小排列
穩定性:對於相同元素,排序后次序保持不變稱為穩定,若次序發生變化稱為不穩定。
排序算法選用的數據結構:
線性表兩種典型的存儲結構為順序表和鏈表,順序表具有隨機存取的特性,存取任意一個元素的時間復雜度為O(1),
鏈表存儲任意一個元素的時間復雜度為O(n)。所以排序算法大多基於順序表構建。
排序算法性能評估:
- 時間復雜度(由於使用次數頻繁,需要着重考慮)
- 空間復雜度(排序算法使用空間通常都不大,不作為主要矛盾)
- 算法復雜度
排序算法宏觀分類
排序算法可以分為內部排序和外部排序,內部排序適用元素不多的文件,可以全部放在內存中執行排序。對於較大文件,由於內存容量的限制,通常使用外部排序。(外部排序比內部排序慢的多)
內部排序分類:
比較排序
- 直接插入排序
- 二分插入排序
- 希爾排序
- 冒泡排序
- 快速排序
- 直接選擇排序
- 堆排序
- 歸並排序
快速排序,堆排序,歸並排序時間復雜度都為O(nlog2n)
希爾排序的時間復雜度與增量序列有關,目前最小能達到O(n1.5)
直接插入排序,冒泡排序,直接選擇排序時間復雜度為O(n2)
非比較排序
- 計數排序
- 桶排序
- 基數排序
時間復雜度為O(n)
插入排序
插入排序基本思想:將待排序的元素按照關鍵碼的大小插入到已經排好序的一組記錄的適當位置上。
直接插入排序(順序查找算法)
對已經排好序的序列挨個遍歷,比較大小,然后移動元素,插入。
PS:從第二個元素開始,先跟第一個元素比。然后第三個元素和1,2,比,第4個元素和前三個比。
如果需要逆序輸出:x[j] > temp改為x[j] < temp
/**
* 直接插入排序
*/
int x[] = new int[]{45,56,84,5,67,91,35,46};
for(int i = 1;i < x.length;i++)
{
int temp = x[i]; //從第二個元素開始遍歷
int j = 0;
for(j = i - 1;j >= 0 && x[j] > temp;j--) //如果第一個元素大於第二個元素
{
x[j + 1] = x[j]; //第一個元素后移
}
x[j + 1] = temp; //將元素插入第一個元素前
//以此類推
}
for (int i = 0; i < x.length; i++)
{
System.out.println(x[i]);
}
}
二分插入排序(二分查找算法)
/**
* 二分插入排序
*/
int x[] = new int[]{45, 56, 84, 5, 67, 91, 35, 46};
for (int i = 1; i < x.length; i++) {
int temp = x[i];
int low = 0;
int high = i - 1;
int mid;
while (low <= high) {
mid = low + (high - low) / 2;
if (x[mid] > temp) {
high = mid - 1;
} else {
low = mid + 1;
}
}
for (int j = i - 1; j >= low; j--) {
x[j + 1] = x[j];
}
x[low] = temp;
}
for (int i = 0; i < x.length; i++) {
System.out.println(x[i]);
}
希爾排序(適用較大規模無序數據)
基本思想:通過分組排序避免大規模數據的遍歷
實現:
對於{15,8,95,63,45,67,26,58}個樹,希爾排序先根據增量序列進行分組,假設第一次增量為8/2=4.
那么分完組就是{15,45},{8,67},{95,26},{63,58}一共四組,每組內部進行排序,比如{15,45}就不用排
然后{95,26}排序為{26,95}。
第一次排序結果為{15,8 ,67,58,45,95,26,63}
增量為4/2=2
分組為{15,67,45,26},{8,58,95,63}
組內進行排序
第二次排序結果為:{15,26,45,67,8,58,63,95}
增量為2/2=1
直接排序
第三次排序結果:{8,15,26,45,58,63,67,95}
希爾排序的復雜度和增量序列是相關的
{1,2,4,8,...}這種序列並不是很好的增量序列,使用這個增量序列的時間復雜度(最壞情形)是O(n^2)
Hibbard提出了另一個增量序列{1,3,7,...,2^k-1},這種序列的時間復雜度(最壞情形)為O(n^1.5)
Sedgewick提出了幾種增量序列,其最壞情形運行時間為O(n^1.3),其中最好的一個序列是{1,5,19,41,109,...}
直接插入排序和二分查找排序在小規模數據且基本有序的情況下比較高效。
希爾排序在大規模數據且無序的情況下高效。
/**
* 希爾排序
*/
int[] x= new int[]{49,38,65,97,76,13,27,49,78,34,12,64,1};
System.out.println("排序之前:");
for(int i=0;i<x.length;i++){
System.out.print(x[i]+" ");
}
System.out.println();
int gap = x.length;
int count =1;
while (true) {
gap = gap/2; //增量每次減半
for (int i = 0; i < gap; i++)
{
for (int j = i + gap; j < x.length; j = j+ gap) {
int temp = x[j];
int k = j - gap;
while (k >= 0 && x[k] > temp) {
x[k + gap] = x[k];
k =k- gap;
}
x[k + gap] = temp;
}
}
System.out.println();
System.out.println("當前增量:"+gap);
System.out.println("第"+count+"次排序結果:");
for(int m=0;m<x.length;m++){
System.out.print(x[m]+" ");
}
System.out.println();
count++;
if (gap == 1)
{ break;}
}
System.out.println();
System.out.println("排序之后:");
for(int i=0;i<x.length;i++){
System.out.print(x[i]+" ");
}
從大至小輸出:
while (k >= 0 && x[k] < temp)
交換排序
冒泡排序
基本思想:比較相鄰的元素,如果第一個比第二個大,元素交換(按照升序排列)
/**
* 冒泡排序
*/
int[] x = new int[]{49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1};
for (int i = 1; i < x.length; i++) {
for (int j = 0; j < x.length - i; j++) {
if (x[j] < x[j + 1]) {
int temp = x[j];
x[j] = x[j + 1];
x[j + 1] = temp;
}
}
}
for(int i=0;i<x.length;i++){
System.out.print(x[i]+" ");
}
快速排序
1、先從數列中取出一個數作為基准數
2、分區過程,將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊
3、再對左右區間重復第二步,直到各區間只有一個數
public static void main(String[] args) {
int[] arr = {1, 4, 5, 67, 2, 7, 8, 6, 9, 44};
quickSort(arr, 0, arr.length-1);
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
}
/**
* 快速排序
*
* @param arr
* @param L 指向數組第一個元素
* @param R 指向數組最后一個元素
*/
public static void quickSort(int[] arr, int L, int R) {
int i = L;
int j = R;
//基准點
int pivot = arr[(L + R) / 2];
System.out.println("基准點為"+pivot);
//左右兩端進行掃描,只要兩端還沒有交替,就一直掃描
while (i <= j) {
//尋找直到比支點大的數
while (pivot > arr[i])
{i++;}
//尋找直到比支點小的數
while (pivot < arr[j])
{j--;}
//此時已經分別找到了比支點小的數(右邊)、比支點大的數(左邊),它們進行交換
if (i <= j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
}
//上面一個while保證了第一趟排序支點的左邊比支點小,支點的右邊比支點大了。
//“左邊”再做排序,直到左邊剩下一個數(遞歸出口)
if (L < j)
{quickSort(arr, L, j);}
//“右邊”再做排序,直到右邊剩下一個數(遞歸出口)
if (i < R)
{quickSort(arr, i, R);}
}
選擇排序
直接選擇排序
第一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,然后再從剩余的未排序元素中尋找到最小(大)元素,然后放到已排序的序列的末尾。以此類推。
int arr[] = new int[] {1, 4, 5, 67, 2, 7, 8, 6, 9, 44};
for (int i = 0; i < arr.length - 1; i++) {
int min = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[min] > arr[j]) {
min = j;
}
}
if (min != i) {
int tmp = arr[min];
arr[min] = arr[i];
arr[i] = tmp;
}
System.out.println("第"+(i+1)+"輪排序最小值"+arr[i]);
System.out.println("第"+(i+1)+"輪排序結果");
for(int m=0;m<arr.length;m++){
System.out.print(arr[m]+" ");
}
System.out.println();
System.out.println();
}
堆排序
堆排序過程
1、構造堆,此時最大值就是堆的頂端
2、將頂端的數和末尾的數交換,固定最大值,然后將剩余的數重新構造堆
3,重復上述過程
public static void main(String[] args) {
int[] array = new int[] { 66,54,87,94,53,25,44,87,76,68,92};
sort(array); //建堆,排序
System.out.println(Arrays.toString(array));
}
public static void sort(int[] array) {
/**
* 為什么只需要遍歷一半的數組?
*
* 堆使用索引計算的方式代替了指針:按照索引的規則,只有0到arr.length、2-1的節點范圍內才存在父節點
*/
System.out.println("建堆:將數組映射為二叉樹:");
for (int i = array.length / 2 - 1; i >= 0; i--) {
//adjustHeap對於單個父節點進行建堆,調整
adjustHeap(array, i, array.length);
}
System.out.println("建堆完成,此時數組為:");
System.out.println(Arrays.toString(array));
System.out.println();
System.out.println("開始排序:");
/**
* 排序
* 建堆完成之后,數組的第一個元素就是最大值
*/
for (int j = array.length - 1; j > 0; j--) {
System.out.println("二叉樹頂端值為:"+array[0]);
// 將數組中最大值也就是index為0的值和末尾的值進行交換
System.out.println("交換末尾和頂端的值");
swap(array, 0, j);
// 末尾值和最大值替換后需要重新調整堆,獲取最大值
System.out.println("重新調整堆");
System.out.println();
adjustHeap(array, 0, j);
}
}
/**
* adjustHeap對於單個父節點進行建堆,調整
*/
public static void adjustHeap(int[] array, int i, int length) {
//temp為某個父節點
int temp = array[i];
//k下標代表父節點的左子節點
for (int k = 2 * i + 1; k < length; ) { //for循環:循環當前父節點及其子樹的順序
//按照索引規則,父節點的左子節點和右子節點的規則是2i+1和2i+2,也就是說,k和k+1是父節點的兩個子節點
if (k + 1 < length && array[k] < array[k + 1]) {
k++;
}
// 如果左右子節點中任意一個大於父節點的值,則進行節點交換
if (array[k] > temp) {
swap(array, i, k);
// 如果子節點和父節點更換,那么父節點新的位置的子節點同樣需要進行判斷,交換
//將子節點的下標賦值給父節點,繼續循環
i = k;
} else
{
break;
}
}
}
/**
* 進行節點間的交換
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
歸並排序
歸並排序的基本思想是將兩個或者兩個以上的有序序列歸並成一個有序序列。
可以分為二路歸並排序和多路歸並排序。