Java-十種內部排序實現(選擇,冒泡,插入,希爾,堆,歸併,快速,基數,計數,桶)及代碼下載

  1. 選擇排序
  2. 冒泡排序
  3. 插入排序
  4. 希爾排序
  5. 堆排序
  6. 歸併排序
  7. 快速排序
  8. 基數排序
  9. 計數排序
  10. 桶排序

1. 選擇排序

這個排序方法最簡單,廢話不多說,直接上代碼:

public class SelectSort {
	/**
	 * 選擇排序
	 * 思路:每次循環得到最小值的下標,然後交換數據。
	 * 如果交換的位置不等於原來的位置,則不交換。
	 */
	public static void main(String[] args) {
		selectSort(Datas.data);
		Datas.prints("選擇排序");
	}
	public static void selectSort(int[] data){
		int index=0;
		for (int i = 0; i < data.length; i++) {
			index = i;
			for (int j = i; j < data.length; j++) {
				if (data[index]>data[j]) {
					index = j;
				}
			}
			if (index != i) {
				swap(data,index,i);
			}
		}
	}
	public static void swap(int[] data,int i,int j){
		int temp = data[i];
		data[i] = data[j];
		data[j] = temp;
	}
}

選擇排序兩層循環,第一個層循環遍歷數組,第二層循環找到剩餘元素中最小值的索引,內層循環結束,交換數據。內層循環每結束一次,排好一位數據。兩層循環結束,數據排好有序。

2 冒泡排序
冒泡排序也簡單,上代碼先:

public class BubbleSort {
	/**
	 * 冒泡排序
	 * 思路:內部循環每走一趟排好一位,依次向後排序
	 */
	public static void main(String[] args) {
		bubbleSort(new int[]{9, 4, 2, 6, 8, 2, 0});
	}

	private static void bubbleSort(int[] data) {
		int temp;
		for (int i = 0; i < data.length; i++) {
			for (int j = 0; j < data.length - i - 1; j++) {
				if (data[j] > data[j+1]) {
					temp =data[j];
					data[j]=data[j+1];
					data[j+1] = temp;
				}
			}
		}

		for (int i = 0; i < data.length; i++) {
			System.out.print(data[i] + ", ");
			
		}
	}
}

冒泡排序和選擇排序有點像,兩層循環,內層循環每結束一次,排好一位數據。不同的是,數據像冒泡一樣,不斷的移動位置,內層循環結束,剛好移動到排序的位置。
這裏寫圖片描述
該圖對應上面的代碼進行的說明,沒有用專門的畫圖工具,使用的是window的maspint,大家湊合着看哈_明白意思就成!

3 插入排序
插入排序也是簡單的排序方法,代碼量不多,先看代碼:

public class InsertSort {
	/**
	 * 插入排序
	 * 思路:將數據插入到已排序的數組中。
	 */
	public static void main(String[] args) {
		int[] data = Datas.data;
		int temp;
		for (int i = 1; i < data.length; i++) {
			temp = data[i];//保存待插入的數值
			int j = i;
			for (; j>0 && temp<data[j-1]; j--) {
				data[j] = data[j-1];
				//如果帶插入的數值前面的元素比該值大,就向後移動一位
			}
			//內部循環結束,找到插入的位置賦值即可。
			data[j]=temp;
		}
		Datas.prints("插入排序");
	}
}

這裏寫圖片描述
該圖是上面插入排序的說明圖,插入排序,其過程就是其名字說明的一樣,將待排序的數據插入到已排序的數據當中。兩層循環,內層循環結束一次,插入排序排好一位數據。

4 希爾排序
希爾排序,也叫縮減增量排序,其中增量的設置影響着程序的性能。最好的增量的設置爲1,3,5,7,11,。。。這樣一組素數,並且各個元素之間沒有公因子。這樣的一組增量 叫做Hibbard增量。使用這種增量的希爾排序的最壞清醒運行時間爲θ(這裏寫圖片描述
當不使用這種增量時,希爾排序的最壞情形運行時間爲θ(這裏寫圖片描述

這裏電腦打印這些太麻煩,乾脆手寫拍照啦哈哈哈哈。。。。。
好了,廢話不多說,上代碼;

public class ShellSort {

	/**
	 * 希爾排序(縮減增量排序)
	 * 想想也不難。
	 * 思路:三層循環
	 * 第一層循環:控制增量-增量隨着程序的進行依次遞減一半
	 * 第二層循環:遍歷數組
	 * 第三層循環:比較元素,交換元素。
	 * 這裏需要注意的是:比較的兩個元素和交換的兩個元素是不同的。
	 */
	public static void main(String[] args) {
		int[] data = Datas.data;
		int k;
		for (int div = data.length/2; div>0; div/=2) {
			for (int j = div; j < data.length; j++) {
				int temp = data[j];
				for (k=j; k>=div && temp<data[k-div] ; k-=div) {
					data[k] = data[k-div];
				}
				data[k] = temp;
			}
		}
		Datas.prints("希爾排序");
	}
}

這裏寫圖片描述
程序中,需要注意的是第三層循環,第三層循環的代碼中,if語句的比較和內部的交換是分別不同的兩個數據。原因是:把大的數據後移,小的數據前移,形成這樣一種趨勢,才能實現排序。
當然可以試試,if語句比較的兩個數據和內部移動的數據一致的話,會出現什麼問題?出現的問題就是移動的數據打破了之前形成的大的數據在後,小的數據在前的趨勢。無法排序。

5 堆排序

堆排序,要知道什麼是堆?說白了,堆就是完全二叉樹,堆是優先隊列。要求父元素比兩個子元素要大。這就好辦了。數組元素構建堆,根節點最大,刪除根節點得到最大值,剩下的元素再次構建堆,接着再刪除根節點,得到第二大元素,剩下的元素再次構建堆,依次類推,得到一組排好序的數據。爲了更好地利用空間,我們把刪除的元素不使用新的空間,而是使用堆的最後一位保存刪除的數據。
代碼上來:

public class HeapSort {
	/**
	 * 堆排序(就是優先隊列)
	 * 也就是完全二叉樹
	 * 第一步:建堆.其實就是講數組中的元素進行下慮操作,
	 * 		使得數組中的元素滿足堆的特性。
	 * 第二步:通過將最大的元素轉移至堆的末尾,
	 * 		然後將剩下的元素在構建堆。
	 * 		完成排序。
	 * 最重要的過程就是構建堆的過程。
	 * 裏面的比較思路和希爾排序中的比較思路一致。
	 * 將大的元素上浮,小的元素下浮。始終和temp比較。
	 * temp除了第一次比較可能改變外,其他次數的比較不改變該值。
	 * 這樣的處理就是讓較大的元素趨於上浮,較小的元素下浮。
	 */
	public static void main(String[] args) {
		int[] data = Datas.data;
		for (int i = data.length/2; i >=0; i--) {
			buildHeap(data,i,data.length);
		}
		Datas.prints("堆排序-構建樹");
		System.out.println("============================");
		for (int i = data.length-1; i>0; i--) {
			swap(data, 0, i);
			buildHeap(data, 0, i);
		}
		Datas.prints("堆排序-排序後");
	}
	static void swap(int[] data,int i,int j){
		int temp = data[i];
		data[i] = data[j];
		data[j] = temp;
	}
	static void buildHeap(int[] data,int i,int len){
		int leftChild = leftChild(i);
		int temp = data[i];
		for (; leftChild<len;) {
			if (leftChild != len-1 && data[leftChild]<data[leftChild+1]) {
				leftChild++;
			}
			if (temp<data[leftChild]) {
				data[i] = data[leftChild];
			}else {
				/**braek說明兩個兒子都比父節點小,
				* 父節點大於兩個兒子
				* 所以直接停止比較,減小比較的次數。
				*/
				break;
			}
			i = leftChild;
			leftChild = leftChild(i);
		}
		data[i] = temp;
	}
	//返回節點i的左兒子的index
	static int leftChild(int i){
		return 2*i+1;
	}
}

這裏寫圖片描述
堆排序,最重要的就是構建堆,構建堆是核心!我們代碼中使用的是數組形式的二叉樹,也就是優先隊列。要真正看懂這部分的代碼,需要知道優先隊列部分的知識,不難,看看就懂啦。說白了就是二叉樹。

6 歸併排序

歸併排序思路就是將兩個已經排好序的數組插入到第三個數組當中。核心就是將原有數組分割兩部分,排好序,插入到第三個與原有數組大小一致的數組中。代碼上來:

public class MergeSort {
	/**
	 * 歸併排序
	 * 思路:如果是兩個已排序的數組,進行合併非常簡單。
	 * 所以就對原有數組進行分割,分割成各個排序的數組,
	 * 然後遞歸合併。
	 */
	public static void main(String[] args) {
		int[] data = Datas.data;
		merge(data);
		Datas.prints("歸併排序");
	}
	public static void merge(int[] data){
		int[] temp = new int[data.length];
		merge0(data, temp, 0, data.length-1);
	}
	public static void merge0(int[] data,int[] temp,int left,int rigth){
		if (left<rigth) {
			int center = (left+rigth)/2;
			merge0(data, temp, left, center);
			merge0(data, temp, center+1, rigth);
			mergeSort(data,temp,left,center,rigth);
		}
	}
	public static void mergeSort(int[] data,int[] temp,int left,int center,int right){
		int leftEnd = center;
		int rightStar = center+1;
		int len = right-left+1;
		int tempPos = left;
		/**
		 * 這裏的三個循環很容易理解。
		 * 其實現實兩個已經排序的數組進行比較,
		 * 將元素添加到temp數組中保存。
		 */
		while (left<=leftEnd&&rightStar<=right) {
			if (data[left]<=data[rightStar]) {
				temp[tempPos++] = data[left++];
			}else {
				temp[tempPos++] = data[rightStar++];
			}
		}
		while (left<=leftEnd) {
			temp[tempPos++]=data[left++];
		}
		while (rightStar<=right) {
			temp[tempPos++]=data[rightStar++];
		}
		/**
		 * 關鍵的一步是下面的拷貝工作。
		 * 爲什麼數組中的拷貝是從right--開始???
		 * 原因是:通過說明圖中,我們知道,元素比較之後,
		 * 會將元素賦值給temp數組相對應的位置上,並不會影響其他位置的數據。
		 * 並且下面的循環中也沒有使用其他位置上面的數據,僅僅拷貝
		 * 本次已經排序的元素。
		 * 下面的拷貝是從right開始,right位置是本次排序最右邊的元素
		 * 其實也可以從left開始,只不過left在上面的排序中值已經改變,
		 * 可以定義一個int leftFlag = left;保存初始最左邊的位置,
		 * 此時下面的循環可以改爲:
		 * for (int i = 0; i < len; i++,leftFlag++) {
		 * 	data[leftFlag]=temp[leftFlag];
		 * }
		 * 運行程序,你會發現,正確輸出結果。
		 */
		for (int i = 0; i < len; i++,right--) {
			data[right]=temp[right];
		}
	}
}

這裏寫圖片描述

說明圖中已經說明了關於數組分割,排序、歸併的步驟。歸併排序其實就是分割,排序,歸併,最後得到排序的結果。
排序要等到分割完成之後進行,歸併要等盜排序之後進行。
分割通過遞歸調用進行,排序通過程序中的三個while循環進行,即完成了歸併。最後將數據拷貝要原來的數組中去。

7 快速排序
快速排序有點類似於歸併排序,其實也是分割,不同的是,快速排序的分割是按照中值進行分割的,所以中值的好壞影響着程序的性能。最常見的情形是三數中值分割法!!
該方法的思路是選取左,右、中三個數進行交換,把三個數中最小值放在左邊,中間值放在中間,最大值放在右邊,這樣以中值爲界形成了兩部分,要注意這時候還沒有排序,只是進行了樞紐元的選取。中間值就是樞紐元!

然後以樞紐元爲中心,分別交換左右兩側的數據,把大的數據放在樞紐元的右側,把小的數據放在樞紐元的左側,最終形成大致排序的兩組數據,樞紐元排好序,然後遞歸調用快速排序。
同時,爲了移動數據方便,我們把樞紐元的位置放在right-1的位置。
大家可能要問了:爲什麼把樞紐元放在right-1的位置呢?
原因是:right的位置放置的是比樞紐元大的數,在選取樞紐元的時候,我們把大的數放在right的位置,最小的數放在left的位置,把樞紐元放在right-1的位置,這樣當完成數據交換之後樞紐元只需一次交換。如果把樞紐元放在中間的位置,要知道的是,中間位置並不一定就是樞紐元要排序的位置。這個地方要搞清楚,還得看代碼:

public class QuickSort {
	/**
	 * 快速排序
	 * 首先找到三數中值,然後分別移動左右兩邊的數據,
	 * 以中值數分割成兩組,一組比中值數大,一組比中值數小。
	 * 然後遞歸快排兩組數組。
	 * 當待排序的數組小於CUTOFF時,使用插入排序。
	 */
	private static int[] datas = {4, 9, 0, 1, 4, 5, -14, -15, 90, 7, 99};
	public static void main(String[] args) {
		quickSort(datas, 0, datas.length-1);

		for (int i = 0; i < datas.length; i++) {
			System.out.print(datas[i] + ", ");
		}
	}
	public static void quickSort(int[] data,int left,int right) {
		int CUTOFF = 1;

		if (left+CUTOFF<right) {
			//找到中值數
			int i = partition(data, left, right);
			//遞歸排序中值數兩邊的數據
			quickSort(data, left, i-1);
			quickSort(data, i+1, right);
		}else {
			/**
			 * 插入排序
			 * 當待排序的元素少於20個時候,
			 * 快速排序性能不如直接插入排序好。
			 * 所以else語句裏面,在待排序基本有序的情況下
			 * 可以使用直接插入排序更好。
			 */
			InsertSort();
		}
	}
	
	private static int partition(int[] data, int left, int right) {
		int media = media3(data, left, right);
		//保存左右界,left,right值不變
		int i =left;
		int j = right-1;
		//循環移動左右兩邊的元素
		while (true) {
			while(data[++i]<media);
			while (data[--j]>media);
			if (i>j) {
				break;
			}
			swap(data, i, j);
		}
		//將中值數移動到i處。中值數即排在i處。
		swap(data, i, right-1);
		return i;
	}
	
	//找到中值數
	public static int media3(int[] data,int left,int right){
		int center = (left+right)/2;
		/**
		 * 前兩個if語句的比較,
		 * 使得最小值放在最左邊。
		 */
		if (data[center]<data[left]) {
			swap(data, center, left);
		}
		if (data[right]<data[left]) {
			swap(data, right, left);
		}
		/**
		 * 第三個if語句使得最大值放在最右邊。
		 * 中間值,放在中間位置。
		 */
		if (data[right]<data[center]) {
			swap(data, right, center);
		}
		//把中間的位置放在right-1的位置。
		swap(data, center, right-1);
		return data[right-1];
	}
	
	//交換數據
	public static void swap(int[] data,int i,int j) {
		int temp = data[i];
		data[i] = data[j];
		data[j] = temp;
	}
	
	private static void InsertSort() {
		for (int i = 1; i < datas.length; i++) {
			for (int j = i; j > 0; j--) {
				if (datas[j] < datas[j-1]) {
					swap(datas, j, j-1);
				}
			}
		}
	}
}

這裏寫圖片描述

快速排序說明圖,示意了i、j遊標的移動位置。結合程序應該能看懂。
不過值得大家注意的是,程序中不僅僅使用快速排序的思路,而在最後,當left與right的差值在CUTOFF的時候,直接使用直接插入排序,不再使用快速排序。原因我在註釋中已經給出。
如果不使用CUTOFF時候的插入排序,最終的結果並不是我們想要的。如果僅僅使用快速排序得到最終結果,則代碼是不正確的。
上面的代碼必須在最後使用一次插入排序才能得到最終的結果。

8 基數排序

桶排序之前不瞭解,我看的《數據結構與算法分析》一書中並沒有給出大量的講解,反而代碼是通過例題的形式給出的。桶排序其實就是形成大的容器,通過比較數據各個位上的數進行排序。
別的不多說了,直接上代碼:

public class RadixSort {
	/**
	 * 基數排序
	 * 二維數組構成桶
	 * 一維數組記錄每個位存放的個數。
	 * 每次構建桶完成,拷貝數據到原來的數組中去。
	 * 繼續下一輪桶的構建。
	 * 分別個位,十位,百位。。。
	 * 程序必須知道最大值的位數。
	 */
	public static void main(String[] args) {
		radixSort(Datas.data,3);
		Datas.prints("基數排序");
	}
	public static void radixSort(int[] data,int maxLen){
		//maxLen表示最大值的長度
		//LSD最低位優先排序  MSD最高位優先排序	l從0開始 循環三次
		int k = 0;
		int n = 1;
		int[][] bucket = new int[10][data.length];//桶
		/**
		 * 表示桶的每一行也就是每一位存放的個數
		 */
		int[] orders = new int[10];
		int temp = 0;
		for (int l = 0; l < maxLen; l++) {
			for (int i = 0; i < data.length; i++) {
				temp = (data[i]/n)%10;
				bucket[temp][orders[temp]] = data[i];
				orders[temp]++;
			}
			//將桶中的數值保存會原來的數組中
			for (int i = 0; i < 10; i++) {
				for (int j = 0; j < orders[i]; j++) {
					if (orders[i]>0) {
						data[k]=bucket[i][j];
						k++;
					}
				}
				//拷貝完成清除記錄的個數,設爲0
				orders[i]=0;
			}
			//n乘以10 取十位  百位的數值
			n*=10;
			k=0;
			//k值記錄拷貝數據到原有數組中的位置,拷貝完成恢復0
		}
	}
}

這裏寫圖片描述
這個是實例,程序打印結果的話,不好看,只好手寫大家看效果。
bucket二維數組存放原始數據。orders數組存放每一位數存放的原始數據的個數。外層循環每執行一次,就把數據拷貝給原來的數組。然後進行下一輪循環。分別進行個位、十位、百位、、、、的循環。這是從最低位開始排序。也有最高位開始進行的排序。

9 計數排序
這個直接上代碼:

public class CuntingSort {

	/**
	 * 計數排序
	 * 思路:構建一個與待排序中最大值相同大小的數組,
	 * 該數組存放待排序數組中每個數字出現的個數。
	 */
	public static void main(String[] args) {
		cunting(Datas.data, 333);
		Datas.prints("計數排序");
	}

	public static void cunting(int[] data,int max){
		int[] temp = new int[max+1];
		int[] result = new int[data.length];
		/**
		 * 該循環設置初始值爲0
		 */
		for (int i = 0; i < temp.length; i++) {
			temp[i]=0;
		}
		/**
		 * 該for語句循環遍歷原數組,將數組中元素出現的個數存放在
		 * temp數組中相對應的位置上。
		 * temp數組長度與最大值的長度一致。保證每個元素都有一個對應的位置。
		 */
		for (int i = 0; i < data.length; i++) {
			temp[data[i]]+=1;
		}
		/**
		 * 累計每個元素出現的個數。
		 * 通過該循環,temp中存放原數組中數據小於等於它的個數。
		 * 也就是說此時temp中存放的就是對應的元素排序後,在數組中存放的位置+1。
		 */
		for (int i = 1; i < temp.length; i++) {
			temp[i]=temp[i]+temp[i-1];
		}
		/**
		 * 這裏從小到大遍歷也可以輸出正確的結果,但是不是穩定的。
		 * 只有從大到小輸出,結果纔是穩定的。
		 * result中存放排序會的結果。
		 */
		for (int i = data.length-1; i>=0; i--) {
			int index = temp[data[i]];
			result[index-1]= data[i];
			temp[data[i]]--;
		}
		Datas.data = result;
	}
}

這個排序還真沒法畫圖,其實這個排序相當容易理解。找出待排序數組中最大的元素,構建一個與最大元素數+1長度的數組temp,這樣保證待排序數組中的每一個元素都能在temp數組中找到自己的位置,但是temp不是用來存放元素的,而是存放每個元素在待排序數組中出現的個數。這一步通過第二個for循環得到。
接着第三個for循環,循環遍歷temp,目的就是得到每個元素排序後存放在原有數組中的位置。大家想一想,每個位置存放自己出現的個數,那麼小於自己的元素出現的個數加到一起,即可得到自己排序後數組中的存放位置。是不是真的很巧妙!!!!!
到此,基本就是該排序算法的核心啦!!!

10 桶排序
桶排序是另外一種以O(n)或者接近O(n)的複雜度排序的算法. 它假設輸入的待排序元素是等可能的落在等間隔的值區間內.一個長度爲N的數組使用桶排序, 需要長度爲N的輔助數組. 等間隔的區間稱爲桶, 每個桶內落在該區間的元素. 桶排序是基數排序的一種歸納結果。
算法的主要思想: 待排序數組A[1…n]內的元素是隨機分佈在[0,1)區間內的的浮點數.輔助排序數組B[0…n-1]的每一個元素都連接一個鏈表. 將A內每個元素乘以N(數組規模)取底,並以此爲索引插入(插入排序)數組B的對應位置的連表中. 最後將所有的鏈表依次連接起來就是排序結果.

這個過程可以簡單的分步如下:
1、設置一個定量的數組當作空桶子
2、尋訪序列,並且把項目一個一個放到對應的桶子去
3、對每個不是空的桶子進行排序
4、從不是空的桶子裏把項目再放回原來的序列中

例如要對大小爲[1…1000]範圍內的n個整數A[1…n]排序,可以把桶設爲大小爲10的範圍,具體而言,設集合B[1]存儲[1…10]的整數,集合B[2]存儲(10…20]的整數,……集合B[i]存儲((i-1)10, i10]的整數,i = 1,2,…100。總共有100個桶。然後對A[1…n]從頭到尾掃描一遍,把每個A[i]放入對應的桶B[j]中。 然後再對這100個桶中每個桶裏的數字排序,這時可用冒泡,選擇,乃至快排,一般來說任何排序法都可以。最後依次輸出每個桶裏面的數字,且每個桶中的數字從小到大輸出,這樣就得到所有數字排好序的一個序列了。

/** 
     * 桶排序算法,對arr進行桶排序,排序結果仍放在arr中 
     * @param arr 
     */  
	public static void main(String[] args){
		bucketSort(Datas.datad);
		for (int i = 0; i < Datas.datad.length; i++) {
			System.out.println(Datas.datad[i]+",");
		}
	}
    public static void bucketSort(double arr[]){  
         
        int n = arr.length;
         
        ArrayList<Double> arrList[] = new ArrayList[n];
        //把arr中的數均勻的的分佈到[0,1)上,每個桶是一個list,存放落在此桶上的元素   
        for(int i =0;i<n;i++){
            int temp = (int) Math.floor(n*arr[i]);
            if(null==arrList[temp])
                arrList[temp] = new ArrayList<>();
            arrList[temp].add(arr[i]);
        }  
         
        //對每個桶中的數進行插入排序   
        for(int i = 0;i<n;i++){  
            if(null!=arrList[i])  
                insert(arrList[i]);  
        }  
         
        //把各個桶的排序結果合併   
        int count = 0; 
         
        for(int i = 0;i<n;i++){  
            if(null!=arrList[i]){  
                Iterator<Double> iter = arrList[i].iterator();  
                while(iter.hasNext()){  
                    Double d = (Double)iter.next();  
                    arr[count] = d;
                    count++;  
                }  
            }  
        }  
    }  
     
    /** 
     * 用插入排序對每個桶進行排序 
     * @param list 
     */  
    public static void insert(ArrayList<Double> list){  
        if(list.size()>1){  
            for(int i =1;i<list.size();i++){  
                if((Double)list.get(i)<(Double)list.get(i-1)){  
                    double temp = (Double) list.get(i);  
                    int j = i-1;  
                    for(;j>=0&&((Double)list.get(j)>(Double)list.get(j+1));j--)  
                        list.set(j+1, list.get(j));  
                    list.set(j+1, temp);  
                }
            }
        }
    }
}

原文地址:http://www.tuicool.com/articles/3emMVz
這個是我看到的關於桶排序說的最好的一篇文章。
舉例說明:
假如待排序列K= { 49、 38 、 35、 97 、 76、 73 、 27、 49 }。這些數據全部在1—100之間。因此我們定製10個桶,然後確定映射函數f(k)=k/10。則第一個關鍵字49將定位到第4個桶中(49/10=4)。依次將所有關鍵字全部堆入桶中,並在每個非空的桶中進行快速排序後得到如下圖所示:
這裏寫圖片描述
對上圖只要順序輸出每個B[i]中的數據就可以得到有序序列了。
這個例子有點類似於基數排序。因此,可以說,桶排序是基數排序的一種。

網上很多講述基數排序,計數排序,桶排序的文章,但是很多都搞混了。如果大家希望看到最權威的講述就看《算法導論》這本書。這本書專門一章講述基數排序,計數排序和桶排序。不過,算法導論有點難呦~~

本文中全部代碼下載,[請猛戳這裏!!]

發佈了110 篇原創文章 · 獲贊 60 · 訪問量 66萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章