1. 程式人生 > >遞迴與排序演算法

遞迴與排序演算法

演算法

遞迴

自己呼叫自己呼叫方法時傳入不同的引數,使程式碼更加簡潔

遞迴的呼叫機制:

  • 每次呼叫方法時,會建立一個新的棧空間(獨立的),以及私有的區域性變數(獨立不會相互影響)

  • 當方法使用的是引用變數時,每個開闢的棧空間共用這一引用變數

  • 遞迴必須無限向遞迴結束條件靠近,否則會出現StackOverFlowError棧溢位錯誤

  • 當方法執行結束或這遇到返回時,誰呼叫就返回到那一層中

遞迴可以解決的問題

  • 問題描述是遞迴的(階乘,斐波那契數列)

  • 問題的解法是遞迴的(漢諾塔問題)

  • 資料結構遞迴的(樹的遍歷,圖的深度優先搜尋)

八大排序演算法

分類:

  1. 內部排序:使用記憶體進行排序

  2. 外部排序:使用外部儲存排序

內部排序分類:

  1. 插入排序

    • 直接插入排序

    • 希爾排序

  2. 交換排序

    • 氣泡排序

    • 快速排序

  3. 選擇排序

    • 簡單選擇排序

    • 堆排序

  4. 歸併排序

  5. 基數排序

氣泡排序-時間複雜度O(n2)


演算法規則:

遍歷陣列如果遇到逆序則進行資料交換

public class BubbleSort
{
public static void main(String[] args)
{
int arr[]= {1,2,3,5,4};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}

//arr【】 待排序陣列
public static void bubbleSort(int arr[]) {
boolean flag=false;//作為標記陣列資料是否發生交換
int temp;//作為交換資料的臨時變數
//外層控制迴圈次數
for(int i=0;i<arr.length-1;i++) {
System.out.println("第"+i+"次排序");
//內層控制資料排序
for(int j=0;j<arr.length-1-i;j++) {
if(arr[j]>arr[j+1]) {
//前面大於後面
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
flag=true;
}
}
if(!flag) {
break;
}else {
flag=false;
}
}

}
}

 

冒泡演算法優化:

定義一個標記變數flag,判斷flag的值是否發生改變,若沒改變則陣列已經有序則可以提前跳出迴圈

選擇排序-時間複雜度O(2)


演算法規則:遍歷陣列,將每次遍歷得到的最小值,與起始位置資料做交換

arr[0]-arr[n-1],找出最小值與arr[0]交換

arr[1]-arr[n-1],找出最小值與arr[1]交換,以此類推

public class SelectSort
{
public static void main(String[] args)
{
int arr[]= {101,34,119,1,-1,90,123};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}

//arr[]待排序陣列
public static void selectSort(int arr[]) {
int min;//最小值記錄
int minIndex;//最小值下標記錄
//外層控制迴圈次數
for(int i=0;i<arr.length-1;i++) {
min=arr[i];
minIndex=i;
//內層從i開始遍歷陣列進行排序
for(int j=i+1;j<arr.length-1;j++) {
//存在小於臨時最小值則替換最小值並記錄下標
if(arr[j]<min){
min=arr[j];
minIndex=j;
}
}
//資料交換
if(minIndex!=i) {
arr[minIndex]=arr[i];
arr[i]=min;

}
}
}
}

選擇排序優化:

判斷最小值位置是否發生改變,若沒有則不需要進行資料交換

插入排序-時間複雜度O(n2)


演算法規則:

假定兩個陣列,一個是原陣列(大小為n-1),一個是有序陣列(大小為1,存放原陣列的第一個元素)。將原陣列中的資料依次取出放入有序陣列中的適當位置,將原陣列資料取出完畢後,則排序完畢

public class InsertSort
{
public static void main(String[] args)
{
int arr[]= {101,34,119,1,-1,89};
insertSort(arr);
System.out.println(Arrays.toString(arr));
}

//arr[]待排序陣列
public static void insertSort(int arr[]) {
int value,insertIndex;
//假定第一個為有序,從下一位開始插入
for(int i=1;i<arr.length;i++) {
value=arr[i];
insertIndex=i-1;
//迴圈條件:插入位置沒到陣列第一位以及插入位置資料大於待插入資料
while(insertIndex>=0 && arr[insertIndex]>value) {
arr[insertIndex+1]=arr[insertIndex];//插入位置資料後移
insertIndex--;//插入位置前移
}
//找到插入位置
if(insertIndex!=i) {
arr[++insertIndex]=value;
}
}
}
}

演算法優化:

判斷插入位置是否發生改變決定是否進行資料插入

希爾排序-時間複雜度O(nlog2n)


演算法規則:

將陣列按照一定增量分組,對每組進行插入排序,繼續減少增量,插入排序,當增量減少為1時,則進行最後的插入排序,結果即為有序的

public class ShellSort
{
public static void main(String[] args)
{
int arr[]= {8,9,1,7,2,3,5,4,6,0,11,0,12,3,7};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
//arr[]待排序陣列
public static void shellSort(int arr[]) {
int value,index;
//定義增量(組數),每次除以2
for(int gap=arr.length/2;gap>0;gap/=2) {
//組內進行插入排序
for(int i=gap;i<arr.length;i++) {
value=arr[i];
//迴圈條件:當插入位置(i-gap)>=0並且插入位置的值大於待插入資料
while(i-gap>=0&&arr[i-gap]>value) {
arr[i]=arr[i-gap];
i-=gap;
}
//找到插入位置
arr[i]=value;
}
}
}
}

快速排序-時間複雜度O(nlog2n)


演算法規則:

將陣列按照大於小於中間值分類,向左向右遞迴得到有序陣列

public class QuickSort
{
public static void main(String[] args)
{
int arr[]= {-9,78,0,23,-567,70};
quickSort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}

//arr[]待排序陣列,left陣列左邊索引,right陣列右邊索引
public static void quickSort(int arr[],int left,int right) {
int l=left;//當前陣列左索引
int r=right;//當前陣列右索引
int pivot=arr[(left+right)/2];//中間值用於判斷資料
int temp;
while(l<r) {
//找左邊大於等於中間值的資料
while(arr[l]<pivot) {
l++;
}
//找右邊大於等於中間值的資料
while(arr[r]>pivot) {
r--;
}
//判斷是否迴圈結束
if(l>=r) {
break;
}
//左右資料交換
temp=arr[l];
arr[l]=arr[r];
arr[r]=temp;

//防止存在相同值時死迴圈
if(arr[l]==pivot) {
r--;
}
if(arr[r]==pivot) {
l++;
}
}
if(l==r) {
l++;
r--;
}
//向左遞迴
if(left<r) {
quickSort(arr,left,r);
}

//向右遞迴
if(l<right) {
quickSort(arr, l, right);
}
}
}

歸併排序-時間複雜度O(nlog2n)


演算法規則:

採用分治策略,將陣列分割值至大小為1的有序陣列再依次進行合併,合併到最終的資料即為有序陣列

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, temp, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}

//分割
public static void mergeSort(int arr[],int temp[],int left,int right) {
if(left<right) {
//計算兩組有序陣列間隔索引(中間索引)
int mid=(left+right)/2;
mergeSort(arr, temp, left, mid);
mergeSort(arr, temp, mid+1, right);
merge(arr, temp, left, right, mid);

}
}

//歸併
//arr[],待排序陣列
//temp[]臨時陣列
//left左邊有序陣列索引
//reght右邊有序陣列索引
//mid中間索引
public static void merge(int arr[],int temp[],int left,int right,int mid) {
int i=left;//左邊索引
int j=mid+1;//右邊索引
int t=0;//臨時陣列索引
//1遍歷兩個有序陣列將資料加入臨時陣列中
while(i<=mid&&j<=right) {
//左邊小
if(arr[i]<arr[j]) {
temp[t]=arr[i];
t++;
i++;
}else {
temp[t]=arr[j];
t++;
j++;
}
}
//2將剩餘陣列資料加入臨時陣列中
while(i<=mid) {
//左邊陣列有剩餘
temp[t]=arr[i];
t++;
i++;
}
while(j<=right) {
//右邊陣列有剩餘
temp[t]=arr[j];
t++;
j++;
}
t=0;
//3拷貝臨時陣列資料到資料中
int innerIndex=left;
while(innerIndex<=right) {
arr[innerIndex]=temp[t];
innerIndex++;
t++;
}
}
}

 

基數排序-時間複雜度O(n*k)

演算法思想:

將資料按照個位十位百位千位等排序,放入代表(0,1,2,3,4,5,6,7,8,9)的十個桶中,進行最大數位數次排序,最終可得到有序陣列

例如541,若按個位排則放入第二個桶中,若按百位排則放入第五個桶

//桶排序
public class BucketSort
{
public static void main(String[] args)
{
int arr[]= {53,3,542,748,12,214};
bucketSort(arr);
System.out.println(Arrays.toString(arr));
}

public static void bucketSort(int arr[]) {
int max=0;
int number;
for(int value:arr) {
if(value>max) {
max=value;
}
}
int maxLength=(max+"").length();
//定義十個桶代表十個數字,每個桶的大小為最大數字的位數
int buckets[][]=new int[10][maxLength];
//定義一個數組記錄每個桶內的數量
int indexs[]=new int[10];

for(int i=1,j=0;j<maxLength;i*=10,j+=1) {
//1將資料按個位十位百位千位數字大小排序放入對應桶內
for(int value:arr) {
number=value/i%10;//計算排序位的值
buckets[number][indexs[number]]=value;//存值
indexs[number]++;//索引+1
}
int index=0;
//2將資料從桶內取出並將索引陣列清零
for(int b=0;b<indexs.length;b++) {
if(indexs[b]!=0) {
//桶內有資料
for(int p=0;p<indexs[b];p++) {
arr[index++]=buckets[b][p];
}
}
indexs[b]=0;
}
}
}
}
!!!此例子僅作整數排序,若做負數排序修改部分程式碼即可,思想一樣,可以參照
   https://code.i-harness.com/zh-CN/q/e98fa9

 

堆排序