日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

arrays中copyof复制两个数组_数据结构与算法(3)数组

發布時間:2023/11/30 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 arrays中copyof复制两个数组_数据结构与算法(3)数组 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

數組(Array)是一種線性表數據結構,利用一組連續的內存空間,存儲一組具有相同類型的數據。

概念介紹

首先我們說一下什么是線性表,線性表就是數據排成一條線的數據結構,每個線性表最多只有前和后兩個方向,數組、鏈表、隊列、棧等都是線性表結構。那么什么是非線性表呢?二叉樹、堆、圖等數據結構就是非線性表,在非線性表中數據之間并不是簡單的前后關系。

其次,數組的內存空間是連續的,數據類型也是相同的,正是因為這兩個特性,數組的隨機訪問速度非常快。我們來看下數組是怎么進行隨機訪問的,假定我們有一個長度為10的int類型的數組int[] a = new int[10],計算機給該數組分配的內存空間為100~110,其中內存塊的首地址base_address=100。當計算機隨機訪問數組中的某個元素時,會先通過尋址公式a[i]_address = base_address + i * data_type_size計算出該元素的內存地址,其中data_type_size代表數組中每個元素的大小,我們的數組是int類型的,所以每個元素就是4個字節。這樣計算出元素的地址后就立馬找到該元素了。

面試的時候我們經常被問到數組和鏈表的區別,有時候我們會回答“鏈表適合插入、刪除,時間復雜度是O(1);數組適合查找,查找的時間復雜度是O(1)”,其實這種描述是不準確的,數組適合查找這是沒問題的,但是時間復雜度不是O(1),即便是用二分查找對排好序的數組進行查找,時間復雜度也是O(Logn),所以,正確的表述應該是“數組支持隨機訪問,根據下標隨機訪問的時間復雜度是O(1)”。

數組聲明

數組聲明有兩種方式:

  • 數據類型 [] 數組名稱 = new 數據類型[數組長度];
  • 數據類型 [] 數組名稱 = {數組元素1,數組元素2,......}
  • 數組實現

    我們知道,一個數組需要具備如下功能:

    • 插入數據
    • 查找數據
    • 刪除數據
    • 迭代數據

    下邊,我們實現一個自己的數組結構:

    public class MyArray { // 定義一個數組 private int[] intArray; // 定義數組的實際有效長度 private int elems; // 定義數組的最大長度 private int length; // 默認構造一個長度為50的數組 public MyArray() { elems = 0; length = 50; intArray = new int[length]; } // 構造函數,初始化一個長度為length 的數組 public MyArray(int length) { elems = 0; this.length = length; intArray = new int[length]; } // 獲取數組的有效長度 public int getSize() { return elems; } /** * 遍歷顯示元素 */ public void display() { for (int i = 0; i < elems; i++) { System.out.print(intArray[i] + " "); } System.out.println(); } /** * 添加元素 * * @param value,假設操作人是不會添加重復元素的,如果有重復元素對于后面的操作都會有影響。 * @return 添加成功返回true,添加的元素超過范圍了返回false */ public boolean add(int value) { if (elems == length) { return false; } else { intArray[elems] = value; elems++; } return true; } /** * 根據下標獲取元素 * * @param i * @return 查找下標值在數組下標有效范圍內,返回下標所表示的元素 查找下標超出數組下標有效值,提示訪問下標越界 */public int get(int i) {if (i < 0 || i > elems) {System.out.println("訪問下標越界");}return intArray[i];}/** * 查找元素 * * @param searchValue * @return 查找的元素如果存在則返回下標值,如果不存在,返回 -1 */public int find(int searchValue) {int i;for (i = 0; i < elems; i++) {if (intArray[i] == searchValue) {break;}}if (i == elems) {return -1;}return i;}/** * 刪除元素 * * @param value * @return 如果要刪除的值不存在,直接返回 false;否則返回true,刪除成功 */public boolean delete(int value) {int k = find(value);if (k == -1) {return false;} else {if (k == elems - 1) {elems--;} else {for (int i = k; i < elems - 1; i++) {intArray[i] = intArray[i + 1];}elems--;}return true;}}/** * 修改數據 * * @param oldValue原值 * @param newValue新值 * @return 修改成功返回true,修改失敗返回false */public boolean modify(int oldValue, int newValue) {int i = find(oldValue);if (i == -1) {System.out.println("需要修改的數據不存在");return false;} else {intArray[i] = newValue;return true;}}}

    插入數據

    前面我們說了,數組的插入和刪除操作效率特別低,這是因為內存空間是連續的,為了保證內存空間的連續性,在插入和刪除時會做很多搬移數據的操作。比如,我們有一個長度為n的數組,現在要將一個數據插入到數組的第k個位置,為了把這個位置騰出來給新來的數據,我們需要將第k~n這部分的元素順序的往后挪一位,如下代碼所示:

    public static int[] insertVal(int[] arr, int insertIndex, int insertVal){ if(insertIndex < 0 || insertIndex > arr.length){ throw new IllegalArgumentException("插入位置錯誤"); } int[] tmpArr = Arrays.copyOf(arr, arr.length + 1); // 將insertIndex后邊的元素一次挪動一位,給新元素騰空,從最后一個元素開始挪 for (int i = tmpArr.length - 1; i > insertIndex; i--) { tmpArr[i] = tmpArr[i - 1]; } tmpArr[insertIndex] = insertVal; return tmpArr;}

    如果在數組的末尾插入元素,那就不需要移動數據了,這時的時間復雜度為O(1)。但如果在數組的開頭插入元素,那所有的數據都需要依次往后移動一位,所以最壞時間復雜度是O(n)。因為我們在每個位置插入元素的概率是一樣的,所以平均情況時間復雜度為 (1+2+…n)/n=O(n)。如果數組中的數據是有序的,我們在某個位置插入一個新的元素時,就必須按照剛才的方法搬移k之后的數據。但是,如果數組中存儲的數據并沒有任何規律,數組只是被當作一個存儲數據的集合。在這種情況下,如果要將某個數組插入到第k個位置,為了避免大規模的數據搬移,我們還有一個簡單的辦法就是,直接將第k位的數據搬移到數組元素的最后,把新的元素直接放入第k個位置。如下代碼所示:

    public static int[] insertVal(int[] arr, int insertIndex, int insertVal){ if(insertIndex < 0 || insertIndex > arr.length){ throw new IllegalArgumentException("插入位置錯誤"); } int[] tmpArr = Arrays.copyOf(arr, arr.length + 1); if(insertIndex == arr.length){ // 插入到最后 tmpArr[insertIndex] = insertVal; } else { tmpArr[arr.length] = arr[insertIndex]; tmpArr[insertIndex] = insertVal; } return tmpArr;}

    利用這種方式,在特定場景下,在第k個位置插入一個元素的時間復雜度就會降為O(1),快速排序算法就是這么干的。

    刪除數據

    和上面的插入數據一樣,如果我們要刪除第k個位置的數據,為了保證內存的連續性,第k之后的數據都要往前挪一位,和插入類似,如果刪除數組末尾的數據,則最好情況時間復雜度為O(1);如果刪除開頭的數據,則最壞情況時間復雜度為O(n);平均情況時間復雜度也為 O(n)。如下代碼所示:

    public static int[] delete(int[] arr, int index) {// 判斷是否合法 if (index >= arr.length || index < 0) { throw new IllegalArgumentException("位置錯誤"); } int[] res = new int[arr.length - 1]; for (int i = 0; i < res.length; i++) { if (i < index) { res[i] = arr[i]; } else { res[i] = arr[i + 1]; } }return res;}

    實際上,在某些特殊場景下,我們可以將多次刪除操作合并執行,例如數組a[8]中存儲了8個元素:a,b,c,d,e,f,g,h。現在,我們要依次刪除 a,b,c三個元素。為了避免 d,e,f,g,h 這幾個數據會被搬移三次,我們可以先記錄下已經刪除的數據。每次的刪除操作并不是真正地搬移數據,只是記錄數據已經被刪除。當數組沒有更多空間存儲數據時,我們再觸發執行一次真正的刪除操作,這樣就大大減少了刪除操作導致的數據搬移。

    以上其實就是JVM的標記清除算法的實現原理(大多數主流虛擬機采用可達性分析算法來判斷對象是否存活,在標記階段,會遍歷所有 GC ROOTS(根對象),將所有GC ROOTS可達的對象標記為存活。只有當標記工作完成后,清理工作才會開始。不足:1.效率問題:標記和清理效率都不高,但是當知道只有少量垃圾產生時會很高效。2.空間問題:會產生不連續的內存空間碎片。)

    數組越界問題

    在C語言中,只要不是訪問受限的內存,所有的內存空間都是可以自由訪問的。所以在C語言中即便數據訪問越界,程序依然是可以執行的,只是這時候程序會出現莫名其妙的執行結果,數組越界在C語言中是一種未決行為,并沒有規定數組訪問越界時編譯器應該如何處理。因為,訪問數組的本質就是訪問一段連續內存,只要數組通過偏移計算得到的內存地址是可用的,那么程序就可能不會報任何錯誤。但是高級語言,如java語言是自帶檢查機制的,如果訪問數據越界會報java.lang.ArrayIndexOutOfBoundsException錯誤。

    容器類與數組的使用場景

    現在很多的編程語言中都提供了容器類,如java語言中的ArrayList,那么在進行開發的時候,什么時候用容器類,什么時候用數組呢?還是以java中的ArrayList為例,這也是我用的最多的容器類,它最大的優勢就是使用方便,已經封裝了一系列的操作,而且不用手動為其擴容,ArrayList支持動態擴容。

    數組定義的時候需要預先指定大小,進而分配連續的存儲空間。如果我們定義的數組大小是10,這時候來了第11個數組元素,我們需要重新分配一塊更大的存儲空間,將原來的數組復制過去(java中已經封裝了工具類System.arraycopy和Arrays.copyOf),然后將新的數據插入。如果使用ArrayList,我們就不需要關心底層的擴容邏輯,ArrayList已經幫我們實現好了,每次空間不夠的時候,它就會將空間自動擴容為1.5倍大小,如下為ArrayList中擴容的代碼:

    /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = **Arrays.copyOf**(elementData, newCapacity);}

    由以上ArrayList的源碼可以看出,其實其內部在擴容時也是封裝了數組的拷貝Arrays.copyOfoldCapacity >> 1右移一位操作,如果該數為正,則高位補0,若為負數,則高位補1,說白了就是除以2。由此可以看出新的列表是老的列表的1.5倍。

    不過因為擴容涉及到內存申請和數據搬移,是比較耗時的,所以,如果我們事先能確定需要存儲的數據大小,最好在創建ArrayList的時候就事先指定數據大小。以下代碼為ArrayList的兩種創建方式:

    /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};............/** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); }}/** * Constructs an empty list with an initial capacity of ten. */public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

    可以看出,如果不指定大小,ArrayList默認就是一個空的對象。在添加元素時,該對象會將大小設置為10,下面為ArrayList的源碼:

    /** * Default initial capacity. */private static final int **DEFAULT_CAPACITY** = 10;............private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return **Math.max(DEFAULT_CAPACITY, minCapacity);** } return minCapacity;}

    言歸正傳,我們接著說數組,ArrayList這一類的集合類已經這么強大了,我們還要數組干什么呢?

    其實很多時候,用數組比用ArrayList這一類的集合類更合適:

    • 1. 比如int、long這一類的基礎數據類型,如果用ArrayList存儲,則需要進行裝箱操作,將其封裝為Integer、Long類,裝箱拆箱操作時需要時間的,有一定性能消耗。所以這時候就可以選擇數組。
    • 2. 如果事先知道數據大小,并且集合類中的大部分方法用不到,操作非常簡單的話就可以用數組。
    • 3. 在表示多維數組時,用數組會更加直觀,如果用集合類,則需要進行嵌套。

    當然,其實很多時候我們沒必要過于追求性能,損耗一丟丟的性能,大部分情況下對系統整體性能沒有什么影響,集合類已經幫我們實現了很多的操作,用起來是非常方便的。但是如果是做底層開發,性能就必須做到極致,這時候優先選擇數組。

    二維數組

    對于 m * n 的數組,m表示這個二維數組有多少個一維數組,表示每一個一維數組的元素有多少個。元素 a[i][j] (i

    • address = base_address + ( i * n + j) * type_size

    二維數組在進行內存分配時,必須知道其一維數組的大小,首先給一個地址值給數組a,然后開始為二位數組的一維數組部分進行分配空間,如果在定義二維數組時,并沒有告訴其二維數組部分的大小,如:數據類型[][] 數組名 = new 數據類型[m][]這時候就無法為其一維數組分配靜態的內存空間,這時候打印其地址值都是null,但是可以動態的分配空間。

    下標之謎

    現在我們思考一個問題,數組的下標為什么從0開始,按照人的思維邏輯,從1開始應該是更合理才是?

    從數組存儲數據的內存模型上來看,“下標”最確切的定義應該是“偏移(offset)”,也就是元素距離數組首地址的偏移量。a[0]也就是偏移量為0的位置,也就是首地址,a[k]表示偏移k個元素類型長度的位置,所以a[k]的內存地址計算公式為:

    a[k]_address = base_address + k * type_size

    但是,如果數組從1開始計數呢,那計算a[k]的內存地址公式就變為:

    a[k]_address = base_address + (k-1)*type_size

    對比以上兩個計算公式,我們會發現,如果數組下標從1開始,每次隨機訪問數組元素時都多了一次減法運算,對于CPU來說,就多了一次減法指令。數組值得稱贊的地方就是通過下標隨機訪問元素的速度,而通過下標隨機訪問數組元素又是非常基礎的編程操作,效率的優化自然要做到極致。為了減少一次減法操作,數組選擇從0開始編號也就是理所當然了。當然還有一方面原因,就是C語言中的數組下標從0開始,其他語言都是在C語言之后出現的,為了減少學習學習成本,盡量模仿C語言中的語法因此也繼續用0開始做下標。

    數組常用操作

    排序

    • 直接排序
    public static void sort(int[] arr) { for (int x = 0; x < arr.length - 1; x++) { for (int y = x + 1; y < arr.length; y++) { if (arr[x] > arr[y]) { int temp = arr[x]; arr[x] = arr[y]; arr[y] = temp; } } }}
    • 冒泡排序
    public static void sort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { boolean f = true;// 每一輪都定義一個開關 // 每次內循環的比較,從0索引開始,每次都在遞減。注意內循環的次數應該是(arr.length - 1 - i)。 for (int j = 0; j < arr.length - 1 - i; j++) { // 比較的索引是j和j+1 if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; f = false;// 發生交換,修改開關的狀態 } } // 此輪結束,查看開關的狀態 if (f) { // 開關狀態沒變,說明已經完成了排序 // 所以,不用繼續下一輪了。 break; } }}
    • 比較排序(選擇排序)
    public static void sort(int[] arr) { // 外層循環控制的是比較的輪數:元素的個數-1 for (int i = 0; i < arr.length - 1; i++) { // 內層控制的是兩兩比較的次數 for (int j = i + 1; j < arr.length; j++) { if (arr[i] > arr[j]) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } }}

    上面這種選擇排序方式可以優化為下面的方式:

    public static void sort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { int index = i; int value = arr[i]; for (int j = i + 1; j < arr.length; j++) { if (arr[j] < value) { index = j; value = arr[j]; } } // 判斷,是否有必要交換兩個元素 if (index != i) { int tmp = arr[i]; arr[i] = arr[index]; arr[index] = tmp; } }}

    這樣可以減少很多數據交換次數。

    • 插入排序
    public static void sort(int[] a) { for (int i = 1; i < a.length; i++) { for (int j = i; j > 0; j--) { if (a[j] < a[j - 1]) { int temp = a[j - 1]; a[j - 1] = a[j]; a[j] = temp; } else break; } }}
    • 快速排序

    快速排序的基本思路如下:

    • 假設我們對數組{7, 1, 3, 5, 13, 9, 3, 6, 11}進行快速排序。
    • 首先在這個序列中找一個數作為基準數,為了方便可以取第一個數。
    • 遍歷數組,將小于基準數的放置于基準數左邊,大于基準數的放置于基準數右邊。
    • 此時得到類似于這種排序的數組{3, 1, 3, 5, 6, 7, 9, 13, 11}。
    • 在初始狀態下7是第一個位置,現在需要把7挪到中間的某個位置k,也即k位置是兩邊數的分界點。
    • 那如何做到把小于和大于基準數7的值分別放置于兩邊呢,我們采用雙指針法,從數組的兩端分別進行比對。
    • 先從最右位置往左開始找直到找到一個小于基準數的值,記錄下該值的位置(記作 i)。
    • 再從最左位置往右找直到找到一個大于基準數的值,記錄下該值的位置(記作 j)。
    • 如果位置i
    • 如果執行到i==j,表示本次比對已經結束,將最后i的位置的值與基準數做交換,此時基準數就找到了臨界點的位置k,位置k兩邊的數組都比當前位置k上的基準值或都更小或都更大。
    • 上一次的基準值7已經把數組分為了兩半,基準值7算是已歸位(找到排序后的位置)。
    • 通過相同的排序思想,分別對7兩邊的數組進行快速排序,左邊對[left, k-1]子數組排序,右邊則是[k+1, right]子數組排序。
    • 利用遞歸算法,對分治后的子數組進行排序。

    快速排序的優勢

    快速排序之所以比較快,是因為相比冒泡排序,每次的交換都是跳躍式的,每次設置一個基準值,將小于基準值的都交換到左邊,大于基準值的都交換到右邊,

    這樣不會像冒泡一樣每次都只交換相鄰的兩個數,因此比較和交換的此數都變少了,速度自然更高。當然,也有可能出現最壞的情況,就是仍可能相鄰的兩個數進行交換。

    快速排序基于分治思想,它的時間平均復雜度很容易計算得到為O(nlogn)。

    實現代碼如下:

    public static void quickSort(int[] array) { int len; if (array == null || (len = array.length) == 0 || len == 1) { return; } sort(array, 0, len - 1);}// 遞歸實現快速排序public static void sort(int[] array, int left, int right) { if (left > right) { return; } // base中存放基準數 int base = array[left]; int i = left, j = right; while (i != j) { // 順序很重要,先從右邊開始往左找,直到找到比base值小的數 while (array[j] >= base && i < j) { j--; } // 再從左往右邊找,直到找到比base值大的數 while (array[i] <= base && i < j) { i++; } // 上面的循環結束表示找到了位置或者(i>=j)了,交換兩個數在數組中的位置 if (i < j) { int tmp = array[i]; array[i] = array[j]; array[j] = tmp; } } // 將基準數放到中間的位置(基準數歸位) array[left] = array[i]; array[i] = base; // 遞歸,繼續向基準的左右兩邊執行和上面同樣的操作 // i的索引處為上面已確定好的基準值的位置,無需再處理 sort(array, left, i - 1); sort(array, i + 1, right);}
    • JDK自帶排序

    Arrays.sort(arr);

    在JDK1.7之前,JDK中自帶的排序算法是經典快排,但是在JDK1.7的時候,JDK中自帶的數組排序算法已經換成了Dual-Pivot Quicksort(雙軸快速排序算法),該算法的時間復雜度是O(nLogn)。

    JDK1.8中的排序算法如下:

    /** * 歸并排序中的最大運行次數 */private static final int MAX_RUN_COUNT = 67;/** * 歸并排序中運行的最大長度 */private static final int MAX_RUN_LENGTH = 33;/** * 如果要排序的數組長度小于此常量,則使用快速排序優先于合并排序。 */private static final int QUICKSORT_THRESHOLD = 286;static void sort(int[] a, int left, int right, int[] work, int workBase, int workLen) { // Use Quicksort on small arrays if (right - left < QUICKSORT_THRESHOLD) { sort(a, left, right, true); return; } /* * Index run[i] is the start of i-th run (ascending or descending * sequence). */ int[] run = new int[MAX_RUN_COUNT + 1]; int count = 0; run[0] = left; // Check if the array is nearly sorted for (int k = left; k < right; run[count] = k) { if (a[k] < a[k + 1]) { // ascending while (++k <= right && a[k - 1] <= a[k]) ; } else if (a[k] > a[k + 1]) { // descending while (++k <= right && a[k - 1] >= a[k]) ; for (int lo = run[count] - 1, hi = k; ++lo < --hi;) { int t = a[lo]; a[lo] = a[hi]; a[hi] = t; } } else { // equal for (int m = MAX_RUN_LENGTH; ++k <= right && a[k - 1] == a[k];) { if (--m == 0) { sort(a, left, right, true); return; } } } /* * The array is not highly structured, use Quicksort instead of * merge sort. */ if (++count == MAX_RUN_COUNT) { sort(a, left, right, true); return; } } // Check special cases // Implementation note: variable "right" is increased by 1. if (run[count] == right++) { // The last run contains one element run[++count] = right; } else if (count == 1) { // The array is already sorted return; } // Determine alternation base for merge byte odd = 0; for (int n = 1; (n <<= 1) < count; odd ^= 1) ; // Use or create temporary array b for merging int[] b; // temp array; alternates with a int ao, bo; // array offsets from 'left' int blen = right - left; // space needed for b if (work == null || workLen < blen || workBase + blen > work.length) { work = new int[blen]; workBase = 0; } if (odd == 0) { System.arraycopy(a, left, work, workBase, blen); b = a; bo = 0; a = work; ao = workBase - left; } else { b = work; ao = 0; bo = workBase - left; } // Merging for (int last; count > 1; count = last) { for (int k = (last = 0) + 2; k <= count; k += 2) { int hi = run[k], mi = run[k - 1]; for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) { if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) { b[i + bo] = a[p++ + ao]; } else { b[i + bo] = a[q++ + ao]; } } run[++last] = hi; } if ((count & 1) != 0) { for (int i = right, lo = run[count - 1]; --i >= lo; b[i + bo] = a[i + ao]) ; run[++last] = right; } int[] t = a; a = b; b = t; int o = ao; ao = bo; bo = o; }}

    有關Dual-Pivot Quicksort(雙軸快速排序算法)的講解可參考如下幾篇文章:

    • https://blog.csdn.net/Holmofy/article/details/71168530
    • https://www.jianshu.com/p/6d26d525bb96
    • https://rerun.me/2013/06/13/quicksorting-3-way-and-dual-pivot/
    • https://www.jianshu.com/p/2c6f79e8ce6e

    數組反轉

    public static void fanzhuan(int[] a) { for (int i = 0; i < a.length / 2; i++) { int tp = a[i]; a[i] = a[a.length - i - 1]; a[a.length - i - 1] = tp; }}

    也可以將數組轉為ArrayList,然后調用Collections.reverse(arrayList);進行反轉

    查找

    最笨的方法,就是從前往后一個個的查找,這種方式不到不得以,不要使用,太笨。

    • 二分查找

    二分查找的實現思路:

    1. 定義查找的范圍,也就是開始索引(如 int start = 0)和結束索引(如 int end = srr.length - 1)。

    2. 判斷 start 是否小于等于 end ,如果 start 大于 end,則結束查找,直接返回-1代表沒有找到所查找的元素。如果滿足條件,則計算出 start 和 end 之間的中間索引 middle ,并獲取該中間索引對應的值 middleVal。

    • int middle = (start + end)/2.
    • int middleVal = arr(middle);

    3. 把中間索引對應的值 middleVal 和要查找的元素 key 進行比較:

    • 如果 middleVal 等于 key,就返回當前的中間索引 middle;
    • 如果 middleVal 大于 key:
    • 對于升序數組:end = middle - 1;
    • 對于降序數組:start = middle + 1;
    • 如果 middleVal 小于 key:
    • 對于升序數組:start = middle + 1;
    • 對于降序數組:end = middle - 1;

    4. 重新執行第二步操作。

    使用二分查找前,必須對數據進行排序,如果未排序,則有可能找不到所查找的元素。如果數組包含多個指定值的元素,則不確定返回哪個位置上的該元素。

    public static int binarySearch(int[] arr, int key) { // 在不斷縮小范圍的過程中,可以 // 返回-1則說明找不到這個數 // 定義起始、終點、中間索引,目標key值索引 int start = 0; int end = arr.length - 1; // 在數組中找要找的數,因為不一定會一下子找到,所以這應該是一個重復尋找的過程,即會用到循環 while (start <= end) {// 看出start不斷增大,end不斷縮小;如果當start和end相等時都還找不到,start會繼續增加,end繼續變小,此時這已經不是一個正常的數組,結束循環 int middle = (start + end) / 2; int value = arr[middle]; // 讓中間索引對應的值value與要查找的值key進行比較 if (key == value) { // 如果相等,即找到,則返回中間索引,并跳出循環 return middle; } else if (key > value) { // key > value if (arr[0] < arr[1]) { // 升序 start = middle + 1; } else { // 降序 end = middle - 1; } } else { // key < value if (arr[0] < arr[1]) { // 升序:end = middle - 1 end = middle - 1; } else {// 降序:start = middle + 1 start = middle + 1; } } } // while括號 return -1;}
    • jdk自帶的二分查找

    Arrays.binarySearch(arr, val);

    public static int binarySearch(int[] a, int key) { return binarySearch0(a, 0, a.length, key);}......private static int binarySearch0(int[] a, int fromIndex, int toIndex, int key) { int low = fromIndex; int high = toIndex - 1; while (low <= high) { int mid = (low + high) >>> 1; int midVal = a[mid]; if (midVal < key) low = mid + 1; else if (midVal > key) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found.}

    數組操作工具類

    public class GenericArray { private T[] data; private int size; // 根據傳入容量,構造Array public GenericArray(int capacity) { data = (T[]) new Object[capacity]; size = 0; } // 無參構造方法,默認數組容量為10 public GenericArray() { this(10); } // 獲取數組容量 public int getCapacity() { return data.length; } // 獲取當前元素個數 public int count() { return size; } // 判斷數組是否為空 public boolean isEmpty() { return size == 0; } // 修改 index 位置的元素 public void set(int index, T e) { checkIndex(index); data[index] = e; } // 獲取對應 index 位置的元素 public T get(int index) { checkIndex(index); return data[index]; } // 查看數組是否包含元素e public boolean contains(T e) { for (int i = 0; i < size; i++) { if (data[i].equals(e)) { return true; } } return false; } // 獲取對應元素的下標, 未找到,返回 -1 public int find(T e) { for (int i = 0; i < size; i++) { if (data[i].equals(e)) { return i; } } return -1; } // 在 index 位置,插入元素e, 時間復雜度 O(m+n) public void add(int index, T e) { checkIndex(index); // 如果當前元素個數等于數組容量,則將數組擴容為原來的2倍 if (size == data.length) { resize(2 * data.length); } for (int i = size - 1; i >= index; i--) { data[i + 1] = data[i]; } data[index] = e; size++; } // 向數組頭插入元素 public void addFirst(T e) { add(0, e); } // 向數組尾插入元素 public void addLast(T e) { add(size, e); } // 刪除 index 位置的元素,并返回 public T remove(int index) { checkIndexForRemove(index); T ret = data[index]; for (int i = index + 1; i < size; i++) { data[i - 1] = data[i]; } size--; data[size] = null; // 縮容 if (size == data.length / 4 && data.length / 2 != 0) { resize(data.length / 2); } return ret; } // 刪除第一個元素 public T removeFirst() { return remove(0); } // 刪除末尾元素 public T removeLast() { return remove(size - 1); } // 從數組中刪除指定元素 public void removeElement(T e) { int index = find(e); if (index != -1) { remove(index); } } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(String.format("Array size = %d, capacity = %d

    總結

    以上是生活随笔為你收集整理的arrays中copyof复制两个数组_数据结构与算法(3)数组的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。