Java陣列的幾種常用操作方法(排序演算法及查詢)
陣列的查詢
查詢是在陣列中尋找特定元素的過程。
線性查詢法
線性查詢法將要查詢的關鍵字key與陣列中的元素逐個進行比較。如果匹配成功,線性查詢法則返回與關鍵字匹配的元素在陣列中的下標;如果沒有匹配成功,則返回-1。下面給出線性查詢法的程式:
private static int LinearSearch(int[] list,int key) {
// TODO Auto-generated method stub
for(int i = 0;i < list.length;i++){
if(key == list[i]){
return i;
}
}
return -1;
}
int[] list = {1,3,5,7,-3};
int number1 = LinearSearch(list,1);
int number2 = LinearSearch(list,7);
int number3 = LinearSearch(list, -5);
System.out.println(number1);
System.out.println(number2);
System.out.println(number3);
輸出結果:
0
3
-1
線性查詢法把關鍵字和陣列中的每一個元素進行比較,這種演算法平均來看,得和陣列中一半的元素進行比較,因此效率不高。
二分查詢法
二分法是另一種常見的對陣列列表的查詢方法。使用二分查詢法的前提條件是陣列中的元素必須先排好序。
二分查詢法首先將關鍵字與陣列的中間元素進行比較,每次比較之後就排除掉一半的陣列元素。對於一個有1024個元素的陣列,在最壞的情況下,二分查詢法只需要比較log2n + 1= 11次,而在最壞的情況下線性查詢要比較1023次。
下面給出二分法查詢的程式:
private static int binarySearch(int[] list ,int key) {
// TODO Auto-generated method stub
int low = 0;
int high = list.length - 1;
//直到low>high時還沒找到關鍵字就結束查詢,返回-1
while(low<=high){
int mid = (low+high)/2;
if(key < list[mid]){
high = mid - 1;
}
else if(key > list[mid]){
low = mid + 1;
}
else if(key == list[mid]){
return mid;
}
}
return -1;
}
用下面的語句跟蹤該方法
int[] list = {1,3,5,7,9,10,34,56,78,99};
int number1 = binarySearch(list,7);
int number2 = binarySearch(list,34);
int number3 = binarySearch(list,100);
System.out.println(number1);
System.out.println(number2);
System.out.println(number3);
輸出結果:
3
6
-1
輸出的只是關鍵字的下標。
二分法效率比線性查詢高,但是要求陣列先排好序。下面我們看陣列的排序。
陣列的排序
像查詢一樣,排序也是計算機中常用的操作。這裡介紹兩種方法。
選擇排序
假設要按升序排列一個數組。選擇排序法先找到數列中最小的數,然後將它放在數列中的最前面。接下來再剩下的數中選擇最小數,將它放在第一個的後面,以此類推,直到數列中僅剩一個數為止。
看一張示例圖,會講解得更清楚:
下面顯示了選擇排序的程式:
public static int[] SelectionSort(int[] arrays){
//i表示交換的次數,從上面的圖中就可以發現交換9次
for (int i = 0; i < arrays.length - 1; i++) {
int temp = 0;//用來儲存最小的那個數
int index = i; // 用來儲存最小值的索引
// 尋找第i個小的數值
//這一次迴圈之後,index將獲得最小值的索引
for (int j = i + 1; j < arrays.length; j++) {
if (arrays[index] > arrays[j]) {
index = j;
}
}
// 交換位置,將找到的第i個小的數值放在第i個位置上
//將最小值賦值給temp
temp = arrays[index];
arrays[index] = arrays[i];
arrays[i] = temp;
}
return arrays;
}
用下面的語句跟蹤該方法
int[] list = {3,5,23,45,1,66};
int[] list2 = SelectionSort(list);
System.out.println(Arrays.toString(list2));
輸出結果:
[1, 3, 5, 23, 45, 66]
eg: 我們也可以用Python來編寫程式獲得同樣的結果:
- 編寫一個用於找出陣列中最小元素的函式findSmallest
def findSmallest(arr):
smallest = arr[0] <-------- 儲存最小的值
smallest_index = 0 <-------- 儲存最小值的索引
for i in range(1, len(arr)):
if arr[i] < smallest:
smallest = arr[i]
smallest_index = i
return smallest_index
def selectionSort(arr): <--------- 對陣列進行排序
newArr = []
for i in range(len(arr)):
smallest = findSmallest(arr) <--------- 找出陣列中最小的元素,將找到的最小元素彈出陣列,
並將其加入到新陣列中
newArr.append(arr.pop(smallest))
return newArr
print selectionSort([5, 3, 6, 2, 10])
插入排序
假設希望對一個數列進行遞增排序。插入排序法的演算法是在已排好序的子數列中反覆插入一個新元素來對數列值進行排序的。
下面展示了運用插入排序演算法對數列{2,9,5,4,8,1,6}進行排序:
private static int[] InsertionSort(int[] list) {
// TODO Auto-generated method stub
// 第1個數肯定是有序的,從第2個數開始遍歷,依次插入有序序列
for(int i = 1;i < list.length;i++){
// 取出第i個數,和前i-1個數比較後,插入合適位置
int currentElement = list[i];
int j;
// 因為前i-1個數都是從小到大的有序序列,所以只要當前比較的數(list[j])是否比currentElement大,就把這個數後移一位
for(j = i-1;j >= 0&& list[j] > currentElement;j--){
list[j+1] = list[j];
}
list[j + 1] = currentElement;
}
return list;
}
用下面的語句跟蹤該方法:
int[] list = {2,9,5,4,8,1,6,-5};
int[] list2 = InsertionSort(list);
System.out.println(Arrays.toString(list2));
輸出結果:
[-5,1, 2, 4, 5, 6, 8, 9]
注意:
在待排資料基本有序的情況下,直接插入排序方法是效果最好。
選擇排序、堆排、歸併、基數演算法的時間複雜度與初始排序無關
n個·字元最壞的情況下至少需要n-1次的兩兩交換才能排好序
氣泡排序
在每次遍歷陣列中,對相鄰的兩個元素進行比較。如果這一對元素是降序,則交換他們的值;否則,保持值不變。
看一個示例圖,有時候圖解比文字更清楚:
下面展示了運用氣泡排序演算法對數列{2,9,5,4,8,1,6}進行排序:
public static double[] bubbleSort(double[] arrays){
int temp;
//這裡i表示交換的幾次,比如上面圖中52換到第一位一共進行了三次交換
for(int i = 0;i<arrays.length-1;i++){
//從後向前依次的比較相鄰兩個數的大小,遍歷一次後,把陣列中第i小的數放在第j個位置上
//這裡j表示交換後它所處的位置,比如,第一次j=3,是因為52跟59交換之後到了3這個位置
//把最後一個數跟前面的數進行比較,一直到i的位置停止
for(int j = arrays.length-1;j>i;j--){
//對相鄰的兩個數進行比較並交換位置
if (arrays[j - 1] > arrays[j]) {
temp = arrays[j - 1];
arrays[j - 1] = arrays[j];
arrays[j] = temp;
}
}
}
return arrays;
}
用下面語句跟蹤上述演算法:
double[] myList = {5.0, 4.4, 1.9, 2.9, 3.4, 2.9, 3.5};
double []list = bubbleSort(myList);
System.out.println("My list after sort is: " + Arrays.toString(list));
輸出結果:
My list after sort is: [1.9, 2.9, 2.9, 3.4, 3.5, 4.4, 5.0]
快速排序
快速排序是交換類的排序,比如在站隊的時候,老師說:“第一個同學出列,其他同學以第一個同學為中心,比他矮的全排在左邊,比他高的全排在右邊。”這就是一趟快速排序。可以看出,一趟快速排序是以一個“樞軸”為中心,將序列分成兩個部分,樞軸的一邊全是比它小(或者小於等於)的,另一邊則全是比它大(或者大於等於)的。
快速排序演算法採用了一種分治的策略,通常稱其為分治法,其基本思想是:
- 1、先從數列中取出一個數作為基準數;
- 2、分割槽過程,將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊;
- 3、再對左右區間重複第二步,直到各區間只有一個數。
以一個簡單的陣列為例,我們來看一下,快速排序演算法的排序過程:
再對array[0…1]和array[3..4]重複上述操作步驟就行了。
注意:在一次查詢中只有i和j的值在變,X的值是一直保持不變的。
以上步驟總結為:
1、i=l,j=r,x=array[i]; 2、j- -從後向前找小於等於x的數,找到後用array[j]替代array[i]; 3、i++從前向後找大於x的數,找到後用array[i]替代array[j]; 4、一直重複執行2、3步驟,直到i=j為止,最後將基準數寫入array[i]。
下面展示瞭如何運用快速排序演算法對陣列[2,7,9,3,5,6,1,8]進行排序:
private static int[] quickSortMethod(int[] array, int left, int right) {
// TODO Auto-generated method stub
if(left<right){
int i = left;//左節點,陣列第一個數的索引
int j = right;//右節點,陣列最後一個數的索引
int x = array[i]; //x為基準數/樞軸,一切以基準數做比較標準
//做一個迴圈,當i=j的時候,將基準數寫入array[i]
while(i<j){
//迴圈從右到左找小於等於x的數,找到則右節點向前移動一位
while(i<j&&array[j]>=x){
j--;
}
//將對應索引的數賦值給左節點對應的數,注意不是賦值給x,x是不變的,左節點前移一位
if(i<j){
array[i] = array[j];
i++;
}
//迴圈從左到右找大於或等於x的數,找到則左節點向前移動一位
while(i<j&&array[i]<x){
i++;
}
//將對應索引的數賦值給右節點對應的數,注意不是賦值給x,x是不變的,右節點前移一位
if(i<j){
array[j] = array[i];
j--;
}
}
//將基準數填入到i
array[i] = x;
//遞迴分別對基準數左右兩邊進行排序
quickSortMethod(array, left, i-1);
quickSortMethod(array, i+1, right);
}
return array;
}
用下面語句跟蹤上述演算法:
int[] array = {2,7,9,3,5,6,1,8};
int left = 0;
int right = array.length-1;
int[] list = quickSortMethod(array,left,right);
System.out.println(Arrays.toString(list));
輸出結果:
[1, 2, 3, 5, 6, 7, 8, 9]
希爾排序
希爾(Shell)排序又稱為縮小增量排序,它是一種插入排序。它是直接插入排序演算法的一種威力加強版。
該方法因DL.Shell於1959年提出而得名。
希爾排序的基本思想是:
把記錄按步長 gap 分組,對每組記錄採用直接插入排序方法進行排序。
隨著步長逐漸減小,所分成的組包含的記錄越來越多,當步長的值減小到 1 時,整個資料合成為一組,構成一組有序記錄,則完成排序。
以一個簡單的陣列為例,我們來看一下,希爾排序演算法的排序過程:
以陣列{26, 53, 67, 48, 57, 13, 48, 32, 60, 50 }為例,步長序列為{5,2,1}
初始化關鍵字: [26, 53, 67, 48, 57, 13, 48, 32, 60, 50 ]
步長gap一般為陣列長度的二分之一;
最後的排序結果:
13 26 32 48 48 50 53 57 60 67
希爾排序基本演算法實現如下:
private static int[] ShellSort(int[] array) {
// TODO Auto-generated method stub
int temp = 0;
int j;
//步長gap,步長會經歷三次變化,10/2=5、5/2=2、2/2=1
//增量序列的最後一個增量值必須等於1才行,
//在這個例子中表明陣列將會經過三趟排序過程,這就是第一個迴圈的作用
//將相隔距離為gap即為5的元素組成一組,可以分為 5 組
for(int gap = array.length/2;gap>0;gap/=2){
//按照直接插入排序的方法對每個組進行排序。
//遍歷從下標為gap的值的地方到最後一個值
for(int i = gap;i<array.length;i++){
//記錄每一次下標為gap的值
temp = array[i];
//遍歷從下標為0的值的地方到下標為gap的值
for(j = i-gap;j>=0;j-=gap){
//直接插入排序。比較兩者的值,然後將較小的和較大的交換位置
if(temp<array[j]){
array[j+gap] = array[j];
}
else {
break;
}
}
array[j+gap] = temp;
}
}
return array;
}
用下面語句跟蹤上述演算法:
int[] array = {9, 1, 2, 5, 7, 4, 8, 6, 3, 5};
int[] list = ShellSort(array);
System.out.println(Arrays.toString(list));
輸出結果:
[1, 2, 3, 4, 5, 5, 6, 7, 8, 9]
二維陣列的排序
首先對二維陣列按行排序,然後按列排序。例如陣列:
{ { 4, 2 }, { 1, 7 }, { 4, 5 }, { 1, 2 }, { 1, 1 }, { 4, 1 } }
package com.example.exercise7_4;
import java.util.Arrays;
import java.util.Scanner;
public class Exercise2 {
public static void main(String[] args) {
int[][] array = { { 4, 2 }, { 1, 7 }, { 4, 5 }, { 1, 2 }, { 1, 1 }, { 4, 1 } };
sort(array);
printArray(array);
}
private static void sort(int[][] array) {
// TODO Auto-generated method stub
for(int i = 0;i < array.length;i++){
double currentMix = array[i][0];
//宣告一個變數記錄是哪一行
int currentMixIndex = i;
for(int j = i;j < array.length;j++){
if(currentMix > array[j][0] || currentMix == array[j][0]
&& array[currentMixIndex][1] > array[j][1]){
currentMix = array[j][0];
currentMixIndex = j;
}
}
// Swap list[i] with list[currentMinIndex] if necessary;
if (currentMixIndex != i) {
int temp0 = array[currentMixIndex][0];
int temp1 = array[currentMixIndex][1];
array[currentMixIndex][0] = array[i][0];
array[currentMixIndex][1] = array[i][1];
array[i][0] = temp0;
array[i][1] = temp1;
}
}
}
private static void printArray(int[][] array) {
// TODO Auto-generated method stub
for (int i = 0; i < array.length; i++) {
System.out.println(array[i][0] + ", " + array[i][1]);
}
}
}