java算法基礎:快速排序、非遞歸快排、二分查找算法、二叉樹、遍歷樹形結構算法、排序算法、歸併排序、堆排序、冒泡排序、生產消費者模式

1 快速排序算法Java實現

1.1 算法概念。
快速排序(Quicksort)是對冒泡排序的一種改進。由C. A. R. Hoare在1962年提出。

1.2 算法思想。
通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。

1.3 實現思路。
①以第一個關鍵字 K 1 爲控制字,將 [K 1 ,K 2 ,…,K n ] 分成兩個子區,使左區所有關鍵字小於等於 K 1 ,右區所有關鍵字大於等於 K 1 ,最後控制字居兩個子區中間的適當位置。在子區內數據尚處於無序狀態。
②把左區作爲一個整體,用①的步驟進行處理,右區進行相同的處理。(即遞歸)
③重複第①、②步,直到左區處理完畢。

1.4 代碼實現(遞歸方式)

static void quicksort(int n[], int left, int right) {
	int dp;
	if (left < right) {
		dp = partition(n, left, right);
		quicksort(n, left, dp - 1);
		quicksort(n, dp + 1, right);
	}
}
static int partition(int n[], int left, int right) {
	int pivot = n[left];
	while (left < right) {
		while (left < right && n[right] >= pivot)
			right--;
		if (left < right)
			n[left++] = n[right];
		while (left < right && n[left] <= pivot)
			left++;
		if (left < right)
			n[right--] = n[left];
	}
	n[left] = pivot;
	return left;
}

1.5 代碼實現(非遞歸方式)

package sort.algorithm;
import java.util.Stack;
//快速排序的非遞歸實現,利用系統的棧stack
public class QuickSortNonRecursion {
 public static void main(String[] args) {
 QuickSortNonRecursion qsnr = new QuickSortNonRecursion();
 int[] array = {0, 2, 11, 121, 18, 99, 3, 5, 101, 22, 9, 100};
 qsnr.quicksort(array);
  for (int i : array) {
		System.out.print(i + "  ");
  }
}
		 
public void quicksort(int[] array) {
	if (array == null || array.length == 1) return;
	//存放開始與結束索引
	Stack<Integer> s = new Stack<Integer>(); 
	//壓棧       
	s.push(0); 
	s.push(array.length - 1); 
	//利用循環裏實現
	while (!s.empty()) { 
		int right = s.pop(); 
		int left = s.pop(); 
		//如果最大索引小於等於左邊索引,說明結束了
		if (right <= left) continue; 
				 
		int i = partition(array, left, right); 
		if (left < i - 1) {
			s.push(left);
			s.push(i - 1);
		} 
		if (i + 1 < right) {
			s.push(i+1);
			s.push(right);
		}
	} 
}
//找到軸心,進行交換
public int partition (int[] data, int first, int end)
{
	int temp;
	int i=first,j=end;
	if(first<end)
	{
		temp=data[i];
		//當i=j的時候,則說明掃描完成了
		while(i<j)
		{
			//從右邊向左邊掃描找到一個小於temp的元素
			while(j>i&&data[j]>temp)j--;
			if(i<j)
			{
				//將該元素賦值給temp
				data[i]=data[j];
				//賦值後就應該將i+1指向下一個序號
				i++;
			}
				   
			//然後從左邊向右邊開始掃描,找到一個大於temp的元素
			while(i<j&&temp>data[i])i++;
			if(i<j)
			{
				//將該元素賦值給temp
				data[j]=data[i];
				//賦值後就應該將j-1指向前一個序號
				j--;
			}
		}
		//將軸數據放在i位置中
		data[i]=temp;
	}
	return i;
}
}

執行結果
在這裏插入圖片描述

2 二分查找算法之JAVA實現

2.1 算法概念。
二分查找算法也稱爲折半搜索、二分搜索,是一種在有序數組中查找某一特定元素的搜索算法。請注意這種算法是建立在有序數組基礎上的。

2.2 算法思想。
①搜素過程從數組的中間元素開始,如果中間元素正好是要查找的元素,則搜素過程結束;
②如果某一特定元素大於或者小於中間元素,則在數組大於或小於中間元素的那一半中查找,而且跟開始一樣從中間元素開始比較。
③如果在某一步驟數組爲空,則代表找不到。
這種搜索算法每一次比較都使搜索範圍縮小一半。

2.3 實現思路。
①找出位於數組中間的值,並存放在一個變量中(爲了下面的說明,變量暫時命名爲temp);
②需要找到的key和temp進行比較;
③如果key值大於temp,則把數組中間位置作爲下一次計算的起點;重複① ②。
④如果key值小於temp,則把數組中間位置作爲下一次計算的終點;重複① ② ③。
⑤如果key值等於temp,則返回數組下標,完成查找。

2.4 實現代碼。
/**

  • @author cherry
  • @create 2019-10-02-15:26
    /
    public class BinarySearch2 {
    public static void main(String[] args) throws Exception {
    Integer[] array = {1, 2, 3, 7, 8, 23, 24};
    System.out.println(binarySearch(array, 0, array.length - 1, 3));
    }
    /
    *
    • @param
    • @param array 需要查找的有序數組
    • @param from 起始下標
    • @param to 終止下標
    • @param key 需要查找的關鍵字
    • @return
    • @throws Exception
      */
      public static <E extends Comparable> int binarySearch(E[] array, int from, int to, E key) throws Exception {
      if (from < 0 || to < 0) {
      throw new IllegalArgumentException(“params from & length must larger than 0 .”);
      }
      if (from <= to) {
      int middle = (from >>> 1) + (to >>> 1); // 右移即除2
      E temp = array[middle];
      if (temp.compareTo(key) > 0) {
      to = middle - 1;
      } else if (temp.compareTo(key) < 0) {
      from = middle + 1;
      } else {
      return middle;
      }
      }
      return binarySearch(array, from, to, key);
      }
      }
      打印查找下標
      在這裏插入圖片描述

3 二叉樹之Java實現

3.1 二叉樹概念
二叉樹是一種非常重要的數據結構,它同時具有數組和鏈表各自的特點:它可以像數組一樣快速查找,也可以像鏈表一樣快速添加。但是他也有自己的缺點:刪除操作複雜。
二叉樹:是每個結點最多有兩個子樹的有序樹,在使用二叉樹的時候,數據並不是隨便插入到節點中的,一個節點的左子節點的關鍵值必須小於此節點,右子節點的關鍵值必須大於或者是等於此節點,所以又稱二叉查找樹、二叉排序樹、二叉搜索樹。
完全二叉樹:若設二叉樹的高度爲h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第h層有葉子結點,並且葉子結點都是從左到右依次排布,這就是完全二叉樹。
滿二叉樹——除了葉結點外每一個結點都有左右子葉且葉子結點都處在最底層的二叉樹。
深度——二叉樹的層數,就是深度。

3.2 二叉樹的特點:
1)樹執行查找、刪除、插入的時間複雜度都是O(logN)
2)遍歷二叉樹的方法包括前序、中序、後序
3)非平衡樹指的是根的左右兩邊的子節點的數量不一致
4) 在非空二叉樹中,第i層的結點總數不超過 , i>=1;
5)深度爲h的二叉樹最多有個結點(h>=1),最少有h個結點;
6)對於任意一棵二叉樹,如果其葉結點數爲N0,而度數爲2的結點總數爲N2,則N0=N2+1;

3.3 二叉樹的Java代碼實現
首先是樹的節點的定義,下面的代碼中使用的是最簡單的int基本數據類型作爲節點的數據,如果要使用節點帶有更加複雜的數據類型,換成對應的對象即可。

public class TreeNode {
 // 左節點
 private TreeNode lefTreeNode;
 // 右節點
 private TreeNode rightNode;
 // 數據
 private int value;
 private boolean isDelete;
 public TreeNode getLefTreeNode() {
  return lefTreeNode;
 }
 public void setLefTreeNode(TreeNode lefTreeNode) {
  this.lefTreeNode = lefTreeNode;
 }
 public TreeNode getRightNode() {
  return rightNode;
 }
 public void setRightNode(TreeNode rightNode) {
  this.rightNode = rightNode;
 }
 public int getValue() {
  return value;
 }
 public void setValue(int value) {
  this.value = value;
 }
 public boolean isDelete() {
  return isDelete;
 }
 public void setDelete(boolean isDelete) {
  this.isDelete = isDelete;
 }
 public TreeNode() {
  super();
 }
 public TreeNode(int value) {
  this(null, null, value, false);
 }
 public TreeNode(TreeNode lefTreeNode, TreeNode rightNode, int value,
   boolean isDelete) {
  super();
  this.lefTreeNode = lefTreeNode;
  this.rightNode = rightNode;
  this.value = value;
  this.isDelete = isDelete;
 }
 @Override
 public String toString() {
  return "TreeNode [lefTreeNode=" + lefTreeNode + ", rightNode="
    + rightNode + ", value=" + value + ", isDelete=" + isDelete
    + "]";
 }
}

下面給出二叉樹的代碼實現。由於在二叉樹中進行節點的刪除非常繁瑣,因此,下面的代碼使用的是利用節點的isDelete字段對節點的狀態進行標識

public class BinaryTree {
 // 根節點
 private TreeNode root;
 public TreeNode getRoot() {
  return root;
 }
 /**
  * 插入操作
  * 
  * @param value
  */
 public void insert(int value) {
  TreeNode newNode = new TreeNode(value);
  if (root == null) {
   root = newNode;
   root.setLefTreeNode(null);
   root.setRightNode(null);
  } else {
   TreeNode currentNode = root;
   TreeNode parentNode;
   while (true) {
    parentNode = currentNode;
    // 往右放
    if (newNode.getValue() > currentNode.getValue()) {
     currentNode = currentNode.getRightNode();
     if (currentNode == null) {
      parentNode.setRightNode(newNode);
      return;
     }
    } else {
     // 往左放
     currentNode = currentNode.getLefTreeNode();
     if (currentNode == null) {
      parentNode.setLefTreeNode(newNode);
      return;
     }
    }
   }
  }
 }
 /**
  * 查找
  * 
  * @param key
  * @return
  */
 public TreeNode find(int key) {
  TreeNode currentNode = root;
  if (currentNode != null) {
   while (currentNode.getValue() != key) {
    if (currentNode.getValue() > key) {
     currentNode = currentNode.getLefTreeNode();
    } else {
     currentNode = currentNode.getRightNode();
    }
    if (currentNode == null) {
     return null;
    }
   }
   if (currentNode.isDelete()) {
    return null;
   } else {
    return currentNode;
   }
  } else {
   return null;
  }
 }
 /**
  * 中序遍歷
  * 
  * @param treeNode
  */
 public void inOrder(TreeNode treeNode) {
  if (treeNode != null && treeNode.isDelete() == false) {
   inOrder(treeNode.getLefTreeNode());
   System.out.println("--" + treeNode.getValue());
   inOrder(treeNode.getRightNode());
  }
 }
}

在上面對二叉樹的遍歷操作中,使用的是中序遍歷,這樣遍歷出來的數據是增序的。
下面是測試代碼:

public class Main {
 public static void main(String[] args) {
  BinaryTree tree = new BinaryTree();
  // 添加數據測試
  tree.insert(10);
  tree.insert(40);
  tree.insert(20);
  tree.insert(3);
  tree.insert(49);
  tree.insert(13);
  tree.insert(123);
  System.out.println("root=" + tree.getRoot().getValue());
  // 排序測試
  tree.inOrder(tree.getRoot());
  // 查找測試
  if (tree.find(10) != null) {
   System.out.println("找到了");
  } else {
   System.out.println("沒找到");
  }
  // 刪除測試
  tree.find(40).setDelete(true);
  if (tree.find(40) != null) {
   System.out.println("找到了");
  } else {
   System.out.println("沒找到");
  }
 }
}

測試排序、查找與刪除
在這裏插入圖片描述

4 遍歷樹形結構之java實現(深度優先+廣度優先)

在編程生活中,我們總會遇見樹性結構,這幾天剛好需要對樹形結構操作,就記錄下自己的操作方式以及過程。現在假設有一顆這樣樹,(是不是二叉樹都沒關係,原理都是一樣的)
在這裏插入圖片描述

4.1 深度優先

英文縮寫爲DFS即Depth First Search.其過程簡要來說是對每一個可能的分支路徑深入到不能再深入爲止,而且每個節點只能訪問一次。對於上面的例子來說深度優先遍歷的結果就是:A,B,D,E,I,C,F,G,H.(假設先走子節點的的左側)。
深度優先遍歷各個節點,需要使用到堆(Stack)這種數據結構。stack的特點是是先進後出。整個遍歷過程如下:
首先將A節點壓入堆中,stack(A);
將A節點彈出,同時將A的子節點C,B壓入堆中,此時B在堆的頂部,stack(B,C);
將B節點彈出,同時將B的子節點E,D壓入堆中,此時D在堆的頂部,stack(D,E,C);
將D節點彈出,沒有子節點壓入,此時E在堆的頂部,stack(E,C);
將E節點彈出,同時將E的子節點I壓入,stack(I,C);
…依次往下,最終遍歷完成,Java代碼大概如下:

public void depthFirst() {
    Stack<Map<String, Object>> nodeStack = new Stack<Map<String, Object>>();
    Map<String, Object> node = new HashMap<String, Object>();
    nodeStack.add(node);
    while (!nodeStack.isEmpty()) {
        node = nodeStack.pop();
        System.out.println(node);
        //獲得節點的子節點,對於二叉樹就是獲得節點的左子結點和右子節點
        List<Map<String, Object>> children = getChildren(node);
        if (children != null && !children.isEmpty()) {
            for (Map child : children) {
                nodeStack.push(child);
            }
        }
    }
}
//節點使用Map存放

4.2 廣度優先

英文縮寫爲BFS即Breadth FirstSearch。其過程檢驗來說是對每一層節點依次訪問,訪問完一層進入下一層,而且每個節點只能訪問一次。對於上面的例子來說,廣度優先遍歷的 結果是:A,B,C,D,E,F,G,H,I(假設每層節點從左到右訪問)。
廣度優先遍歷各個節點,需要使用到隊列(Queue)這種數據結構,queue的特點是先進先出,其實也可以使用雙端隊列,區別就是雙端隊列首位都可以插入和彈出節點。整個遍歷過程如下:
首先將A節點插入隊列中,queue(A);
將A節點彈出,同時將A的子節點B,C插入隊列中,此時B在隊列首,C在隊列尾部,queue(B,C);
將B節點彈出,同時將B的子節點D,E插入隊列中,此時C在隊列首,E在隊列尾部,queue(C,D,E);
將C節點彈出,同時將C的子節點F,G,H插入隊列中,此時D在隊列首,H在隊列尾部,queue(D,E,F,G,H);
將D節點彈出,D沒有子節點,此時E在隊列首,H在隊列尾部,queue(E,F,G,H);
…依次往下,最終遍歷完成,Java代碼大概如下:

public void breadthFirst() {
    Deque<Map<String, Object>> nodeDeque = new ArrayDeque<Map<String, Object>>();
    Map<String, Object> node = new HashMap<String, Object>();
    nodeDeque.add(node);
    while (!nodeDeque.isEmpty()) {
        node = nodeDeque.peekFirst();
        System.out.println(node);
        //獲得節點的子節點,對於二叉樹就是獲得節點的左子結點和右子節點
        List<Map<String, Object>> children = getChildren(node);
        if (children != null && !children.isEmpty()) {
            for (Map child : children) {
                nodeDeque.add(child);
            }
        }
    }
}
//這裏使用的是雙端隊列,和使用queue是一樣的

5 排序算法Java實現

5.1 歸併排序之java實現

歸併排序(Merge)是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每個子序列是有序的。然後再把有序子序列合併爲整體有序序列。
歸併排序是建立在歸併操作上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。 將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲2-路歸併。
歸併排序算法穩定,數組需要O(n)的額外空間,鏈表需要O(log(n))的額外空間,時間複雜度爲O(nlog(n)),算法不是自適應的,不需要對數據的隨機讀取。
工作原理:
1)申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列
2)設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置
3)比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置
4)重複步驟3直到某一指針達到序列尾
5)將另一序列剩下的所有元素直接複製到合併序列尾

public class MergeSortTest {  
    public static void main(String[] args) {  
        int[] data = new int[] { 5, 3, 6, 2, 1, 9, 4, 8, 7 };  
        print(data);  
        mergeSort(data);  
        System.out.println("排序後的數組:");  
        print(data);  
    }  
    public static void mergeSort(int[] data) {  
        sort(data, 0, data.length - 1);  
    }  
    public static void sort(int[] data, int left, int right) {  
        if (left >= right)  
            return;  
        // 找出中間索引  
        int center = (left + right) / 2;  
        // 對左邊數組進行遞歸  
        sort(data, left, center);  
        // 對右邊數組進行遞歸  
        sort(data, center + 1, right);  
        // 合併  
        merge(data, left, center, right);  
        print(data);  
    }  
    /** 
     * 將兩個數組進行歸併,歸併前面2個數組已有序,歸併後依然有序 
     * @param data   數組對象 
     * @param left   左數組的第一個元素的索引 
     * @param center 左數組的最後一個元素的索引,center+1是右數組第一個元素的索引 
     * @param right  右數組最後一個元素的索引 
     */  
    public static void merge(int[] data, int left, int center, int right) {  
        // 臨時數組  
        int[] tmpArr = new int[data.length];  
        // 右數組第一個元素索引  
        int mid = center + 1;  
        // third 記錄臨時數組的索引  
        int third = left;  
        // 緩存左數組第一個元素的索引  
        int tmp = left;  
        while (left <= center && mid <= right) {  
            // 從兩個數組中取出最小的放入臨時數組  
            if (data[left] <= data[mid]) {  
                tmpArr[third++] = data[left++];  
            } else {  
                tmpArr[third++] = data[mid++];  
            }  
        }  
        // 剩餘部分依次放入臨時數組(實際上兩個while只會執行其中一個)  
        while (mid <= right) {  
            tmpArr[third++] = data[mid++];  
        }  
        while (left <= center) {  
            tmpArr[third++] = data[left++];  
        }  
        // 將臨時數組中的內容拷貝回原數組中  
        // (原left-right範圍的內容被複制回原數組)  
        while (tmp <= right) {  
            data[tmp] = tmpArr[tmp++];  
        }  
    }  
    public static void print(int[] data) {  
        for (int i = 0; i < data.length; i++) {  
            System.out.print(data[i] + "\t");  
        }  
        System.out.println();  
    }  
}  

運行結果:
[java] view plain copy
5 3 6 2 1 9 4 8 7
3 5 6 2 1 9 4 8 7
3 5 6 2 1 9 4 8 7
3 5 6 1 2 9 4 8 7
1 2 3 5 6 9 4 8 7
1 2 3 5 6 4 9 8 7
1 2 3 5 6 4 9 7 8
1 2 3 5 6 4 7 8 9
1 2 3 4 5 6 7 8 9
排序後的數組:
1 2 3 4 5 6 7 8 9

5.2 堆排序之java實現

堆積排序(Heapsort)是指利用堆積樹(堆)這種資料結構所設計的一種排序算法,可以利用數組的特點快速定位指定索引的元素。堆排序是不穩定的排序方法,輔助空間爲O(1), 最壞時間複雜度爲O(nlog2n) ,堆排序的堆序的平均性能較接近於最壞性能。
堆排序利用了大根堆(或小根堆)堆頂記錄的關鍵字最大(或最小)這一特徵,使得在當前無序區中選取最大(或最小)關鍵字的記錄變得簡單。

1)用大根堆排序的基本思想
① 先將初始文件R[1…n]建成一個大根堆,此堆爲初始的無序區
② 再將關鍵字最大的記錄R1和無序區的最後一個記錄R[n]交換,由此得到新的無序區R[1…n-1]和有序區R[n],且滿足R[1…n-1].keys≤R[n].key
③由於交換後新的根R[1]可能違反堆性質,故應將當前無序區R[1…n-1]調整爲堆。然後再次將R[1…n-1]中關鍵字最大的記錄R[1]和該區間的最後一個記錄R[n-1]交換,由此得到新的無序區R[1…n-2]和有序區R[n-1…n],且仍滿足關係R[1…n-2].keys≤R[n-1…n].keys,同樣要將R[1…n-2]調整爲堆。
……
直到無序區只有一個元素爲止。

2)大根堆排序算法的基本操作:
① 初始化操作:將R[1…n]構造爲初始堆;
② 每一趟排序的基本操作:將當前無序區的堆頂記錄R[1]和該區間的最後一個記錄交換,然後將新的無序區調整爲堆(亦稱重建堆)。
注意:
①只需做n-1趟排序,選出較大的n-1個關鍵字即可以使得文件遞增有序。
②用小根堆排序與利用大根堆類似,只不過其排序結果是遞減有序的。堆排序和直接選擇排序相反:在任何時刻堆排序中無序區總是在有序區之前,且有序區是在原向量的尾部由後往前逐步擴大至整個向量爲止。
代碼實現:

public class HeapSortTest {   
public static void main(String[] args) {  
    int[] data5 = new int[] { 5, 3, 6, 2, 1, 9, 4, 8, 7 };  
    print(data5);  
    heapSort(data5);  
    System.out.println("排序後的數組:");  
    print(data5);  
}  

public static void swap(int[] data, int i, int j) {  
    if (i == j) {  
        return;  
    }  
    data[i] = data[i] + data[j];  
    data[j] = data[i] - data[j];  
    data[i] = data[i] - data[j];  
}  

public static void heapSort(int[] data) {  
    for (int i = 0; i < data.length; i++) {  
        createMaxdHeap(data, data.length - 1 - i);  
        swap(data, 0, data.length - 1 - i);  
        print(data);  
    }  
}  

public static void createMaxdHeap(int[] data, int lastIndex) {  
    for (int i = (lastIndex - 1) / 2; i >= 0; i--) {  
        // 保存當前正在判斷的節點  
        int k = i;  
        // 若當前節點的子節點存在  
        while (2 * k + 1 <= lastIndex) {  
            // biggerIndex總是記錄較大節點的值,先賦值爲當前判斷節點的左子節點  
            int biggerIndex = 2 * k + 1;  
            if (biggerIndex < lastIndex) {  
                // 若右子節點存在,否則此時biggerIndex應該等於 lastIndex  
                if (data[biggerIndex] < data[biggerIndex + 1]) {  
                    // 若右子節點值比左子節點值大,則biggerIndex記錄的是右子節點的值  
                    biggerIndex++;  
                }  
            }  
            if (data[k] < data[biggerIndex]) {  
      // 若當前節點值比子節點最大值小,則交換2者得值,交換後將biggerIndex值賦值給k  
                swap(data, k, biggerIndex);  
                k = biggerIndex;  
            } else {  
                break;  
            }  
        }  
    }  
}  

public static void print(int[] data) {  
    for (int i = 0; i < data.length; i++) {  
        System.out.print(data[i] + "\t");  
    }  
    System.out.println();  
}  
}  

運行結果:
[java] view plain copy
5 3 6 2 1 9 4 8 7
3 8 6 7 1 5 4 2 9
2 7 6 3 1 5 4 8 9
4 3 6 2 1 5 7 8 9
4 3 5 2 1 6 7 8 9
1 3 4 2 5 6 7 8 9
2 3 1 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
排序後的數組:
1 2 3 4 5 6 7 8 9

5.3 冒泡排序之java實現

原理:比較兩個相鄰的元素,將值大的元素交換至右端。
思路:依次比較相鄰的兩個數,將小數放在前面,大數放在後面。即在第一趟:首先比較第1個和第2個數,將小數放前,大數放後。然後比較第2個數和第3個數,將小數放前,大數放後,如此繼續,直至比較最後兩個數,將小數放前,大數放後。重複第一趟步驟,直至全部排序完成。
舉例說明:要排序數組:int[] arr={6,3,8,2,9,1};
第一趟排序:
  第一次排序:6和3比較,6大於3,交換位置: 3 6 8 2 9 1
  第二次排序:6和8比較,6小於8,不交換位置:3 6 8 2 9 1
  第三次排序:8和2比較,8大於2,交換位置: 3 6 2 8 9 1
  第四次排序:8和9比較,8小於9,不交換位置:3 6 2 8 9 1
  第五次排序:9和1比較:9大於1,交換位置: 3 6 2 8 1 9
  第一趟總共進行了5次比較, 排序結果: 3 6 2 8 1 9
第二趟排序:
  第一次排序:3和6比較,3小於6,不交換位置:3 6 2 8 1 9
  第二次排序:6和2比較,6大於2,交換位置: 3 2 6 8 1 9
  第三次排序:6和8比較,6大於8,不交換位置:3 2 6 8 1 9
  第四次排序:8和1比較,8大於1,交換位置: 3 2 6 1 8 9
  第二趟總共進行了4次比較, 排序結果: 3 2 6 1 8 9
第三趟排序:
  第一次排序:3和2比較,3大於2,交換位置: 2 3 6 1 8 9
  第二次排序:3和6比較,3小於6,不交換位置:2 3 6 1 8 9
  第三次排序:6和1比較,6大於1,交換位置: 2 3 1 6 8 9
  第二趟總共進行了3次比較, 排序結果: 2 3 1 6 8 9
第四趟排序:
  第一次排序:2和3比較,2小於3,不交換位置:2 3 1 6 8 9
  第二次排序:3和1比較,3大於1,交換位置: 2 1 3 6 8 9
  第二趟總共進行了2次比較, 排序結果: 2 1 3 6 8 9
第五趟排序:
  第一次排序:2和1比較,2大於1,交換位置: 1 2 3 6 8 9
  第二趟總共進行了1次比較, 排序結果: 1 2 3 6 8 9
最終結果:1 2 3 6 8 9
由此可見:N個數字要排序完成,總共進行N-1趟排序,每i趟的排序次數爲(N-i)次,所以可以用雙重循環語句,外層控制循環多少趟,內層控制每一趟的循環次數,即
for(int i=1;i<arr.length;i++){
for(int j=1;j<arr.length-i;j++){
//交換位置
}
冒泡排序的優點:每進行一趟排序,就會少比較一次,因爲每進行一趟排序都會找出一個較大值。如上例:第一趟比較之後,排在最後的一個數一定是最大的一個數,第二趟排序的時候,只需要比較除了最後一個數以外的其他的數,同樣也能找出一個最大的數排在參與第二趟比較的數後面,第三趟比較的時候,只需要比較除了最後兩個數以外的其他的數,以此類推……也就是說,沒進行一趟比較,每一趟少比較一次,一定程度上減少了算法的量。
用時間複雜度來說:
1)如果我們的數據正序,只需要走一趟即可完成排序。所需的比較次數和記錄移動次數均達到最小值,即:Cmin=n-1;Mmin=0;所以,冒泡排序最好的時間複雜度爲O(n)。
2)如果很不幸我們的數據是反序的,則需要進行n-1趟排序。每趟排序要進行n-i次比較(1≤i≤n-1),且每次比較都必須移動記錄三次來達到交換記錄位置。在這種情況下,比較和移動次數均達到最大值:冒泡排序的最壞時間複雜度爲:O(n2) 。
綜上所述:冒泡排序總的平均時間複雜度爲:O(n2) 。
代碼實現:

/*
 * 冒泡排序
 */
public class BubbleSort {
  public static void main(String[] args) {
    int[] arr={6,3,8,2,9,1};
    System.out.println("排序前數組爲:");
    for(int num:arr){
      System.out.print(num+" ");
    }
    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]){
          int temp=arr[j];
          arr[j]=arr[j+1];
          arr[j+1]=temp;
        }
      }
    } 
    System.out.println();
    System.out.println("排序後的數組爲:");
    for(int num:arr){
      System.out.print(num+" ");
    } 
  }
}

我的代碼

package bubblesort;
import java.util.Arrays;
/**
 * @author cherry
 *  冒泡排序 時間複雜度 O(n^2) 空間複雜度O(1)
 * @create 2019-10-02-10:25
 */
public class BubbleSort {
    public static void main(String[] args) {
        int[] data = {1, 2, 5, 3, 4, 12, 7, 64, 8,7,-1};
        System.out.println("排序前:" + Arrays.toString(data));
        System.out.println("排序後:"+Arrays.toString(bubbleSort(data)));
    }

    static int[] bubbleSort(int[] data) {
        for (int i = 0; i < data.length - 1; i++) {
            boolean flag = false;
            for (int j = 0; j < data.length - 1 - i; j++) {
                if (data[j] > data[j + 1]) {
                    //將元素大小交換
                    int temp = data[j + 1];
                    data[j + 1] = data[j];
                    data[j] = temp;
                    flag = true;
                }
            }
            System.out.println(Arrays.toString(data));//排序過程
    //排序順序正確後不再繼續執行
            if (!flag) break;
        }
        return data;
    }
}

查看排序過程
在這裏插入圖片描述

5.4 生產者消費者模式之java實現

重點使用BlockingQueue,它也是java.util.concurrent下的主要用來控制線程同步的工具。
BlockingQueue有四個具體的實現類,根據不同需求,選擇不同的實現類
1)ArrayBlockingQueue:一個由數組支持的有界阻塞隊列,規定大小的BlockingQueue,其構造函數必須帶一個int參數來指明其大小.其所含的對象是以FIFO(先入先出)順序排序的。
2)LinkedBlockingQueue:大小不定的BlockingQueue,若其構造函數帶一個規定大小的參數,生成的BlockingQueue有大小限制,若不帶大小參數,所生成的BlockingQueue的大小由Integer.MAX_VALUE來決定.其所含的對象是以FIFO(先入先出)順序排序的。
3)PriorityBlockingQueue:類似於LinkedBlockQueue,但其所含對象的排序不是FIFO,而是依據對象的自然排序順序或者是構造函數的Comparator決定的順序。
4)SynchronousQueue:特殊的BlockingQueue,對其的操作必須是放和取交替完成的。
LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的話,默認最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在隊列滿的時候會阻塞直到有隊列成員被消費,take方法在隊列空的時候會阻塞,直到有隊列成員被放進來。
生產者消費者的示例代碼:
生產者:

import java.util.concurrent.BlockingQueue;  
public class Producer implements Runnable {  
    BlockingQueue<String> queue;  
    public Producer(BlockingQueue<String> queue) {  
        this.queue = queue;  
    }  
    @Override  
    public void run() {  
        try {  
            String temp = "A Product, 生產線程:"  
                    + Thread.currentThread().getName();  
            System.out.println("I have made a product:"  
                    + Thread.currentThread().getName());  
            queue.put(temp);//如果隊列是滿的話,會阻塞當前線程  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  

消費者:

import java.util.concurrent.BlockingQueue;  
public class Consumer implements Runnable{  
BlockingQueue<String> queue;  
  
public Consumer(BlockingQueue<String> queue){  
    this.queue = queue;  
}  
@Override  
public void run() {  
    try {  
        String temp = queue.take();//如果隊列爲空,會阻塞當前線程  
        System.out.println(temp);  
    } catch (InterruptedException e) {  
        e.printStackTrace();  
    }  
}  
}

測試類:

import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.BlockingQueue;  
import java.util.concurrent.LinkedBlockingQueue;  
  
public class Test3 {  

public static void main(String[] args) {  
    BlockingQueue<String> queue = new LinkedBlockingQueue<String>(2);  
    // BlockingQueue<String> queue = new LinkedBlockingQueue<String>();  
    //不設置的話,LinkedBlockingQueue默認大小爲Integer.MAX_VALUE  
    // BlockingQueue<String> queue = new ArrayBlockingQueue<String>(2);  
    Consumer consumer = new Consumer(queue);  
    Producer producer = new Producer(queue);  
    for (int i = 0; i < 5; i++) {  
        new Thread(producer, "Producer" + (i + 1)).start();  
        new Thread(consumer, "Consumer" + (i + 1)).start();  
    }  
}  
}  

在這裏插入圖片描述
由於隊列的大小限定成了2,所以最多隻有兩個產品被加入到隊列當中,而且消費者取到產品的順序也是按照生產的先後順序,原因就是LinkedBlockingQueue和ArrayBlockingQueue都是按照FIFO的順序存取元素的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章