91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

數據結構中的數組

發布時間:2020-07-22 19:04:40 來源:網絡 閱讀:6778 作者:18061389 欄目:軟件技術

我們要學習的第一個數據結構就是數組,數組中很多值得挖掘。

數組基礎

把數據碼成一排進行存放

數組中索引從0開始,Java語法中要求數組存放同一類型的元素,可以通過中括號下標的方式取到元素。

這樣可以看到Main中有的方法。

package cn.mtianyan;

public class Main {

public static void main(String[] args) {
    // 必須傳入長度
    int[] arr = new int[10];
    for (int i = 0; i < arr.length; i++) {
        arr[i] = i;
    }

    System.out.println("=========");

    int[] scores = new int[]{100,99,96};
    for (int i = 0; i < scores.length; i++) {
        System.out.println(scores[i]);
    }

    System.out.println("=========");

    for (int score : scores) {
        System.out.println(score);
    }

    System.out.println("=========");
    scores[0] = 92;

    for (int score : scores) {
        System.out.println(score);
    }
}

}

數組可以這樣遍歷,是因為它實現了可遍歷接口。

Java為我們提供的數組,其中非常重要的一部分就是它的索引。索引可以有語義,也可以沒有語義,2可以代表學號,但也可以將其視為沒有語義。

數組最大的優點: 快速查詢,scores[2]直接插到2號索引的。因此數組最好應用于"索引有語義"的情況,查學號為2的學生成績。

但并非所有有語意的索弓都適用于數組,如×××號: 1101031985121 66666;依次為索引,這得開辟很大的內存空間,空間浪費。

數組也可以處理“索引沒有語意”的情況。我們在這一章,主要處理“索引沒有語意”的情況數組的使用。

索引沒有語意,如何表示沒有元素? capacity 和 size的區別: 數組size是3,capacity是8。

如何添加元素(超過size如何處理)?如何刪除元素(挪位)? Java中的數組并沒有這些方法,我們需要基于java的數組,二 次封裝屬于我們自己的數組類。

我們自己做的是動態數組(內部依然使用java數組實現),Java的是靜態數組。

增刪改查,有的數據結構不一定四個齊全。capacity是數組最多可以裝多少元素,和實際能裝多少元素是沒有關系的。

package cn.mtianyan;

public class Array {
private int[] data;
private int size;

/**
 * 帶容量參數構造函數
 *
 * @param capacity 數組容量
 */
public Array(int capacity) {
    data = new int[capacity];
    size = 0;
}

/**
 * 默認構造函數
 */
public Array() {
    this(10);
}

/**
 * 靜態數組入參構造函數
 *
 * @param data 傳入靜態數組
 */
public Array(int[] data) {
    this.data = data;
}

/**
 * 獲取數組元素個數
 *
 * @return size 數組元素個數
 */
public int getSize() {
    return size;
}

/**
 * 獲取數組的容量
 *
 * @return capacity 獲取容量
 */
public int getCapacity(){
    return data.length;
}

/**
 * 判斷數組是否為空
 *
 * @return 是否為空
 */
public boolean isEmpty(){
    return size == 0;
}

}
向數組中添加元素

向數組末尾添加元素

size指向data中第一個為空位置。因此添加元素時只需要添加到arr[size],并size++即可。(注意添加元素判滿)

/**
 * 向所有元素末尾添加一個新元素。
 *
 * @param e 添加的元素
 */
public void addLast(int e){

// if (isFull()){
// throw new IllegalArgumentException("AddLast failed. Array is Full");
// }else {
// data[size] =e; // data[size++] =e;
// size++;
// }
add(size,e);
}

/**
 * 向索引0號位置添加元素
 *
 * @param e 添加的元素
 */
public void addFirst(int e){
    add(0,e);
}

/**
 * 在index位置插入一個新元素e
 *
 * @param index 插入位置索引
 * @param e 插入元素
 */
public void add(int index,int e){
    if (isFull())
        throw new IllegalArgumentException("AddLast failed. Array is Full");
    // 大于size就不是緊密排列了
    if (index<0 || index>size){
        throw new IllegalArgumentException("AddLast failed. Required index<0 or index>size ");
    }
    else {
        // 從哪開始挪呢: 從size-1這個元素(size本身是指向空的),挪到哪個呢,index位置的這個元素也是要挪的。
        for (int i=size-1; i>=index; i--){
            data[i+1] = data[i]; // 后一個等于前一個,從數組最后一個元素開始。
            // 極端值驗證: size 1 index 0;(i=0;i>=0;i--)會執行一次data[1]=data[0].正確。
        }
        data[index] = e;
        size++;
    }
}

77插入,后面的元素都要向后挪動。

這里注意必須從100這里,向后挪,否則會造成值覆蓋。

在數組中查詢元素和修改元素

/**
 * 打印數組信息及遍歷元素。
 *
 * @return 數組信息和元素遍歷結果
 */
@Override
public String toString() {
    StringBuilder res = new StringBuilder();
    res.append(String.format("Array: size = %d, capacity = %d\n",size,data.length));
    res.append('[');
    for (int i = 0; i < size; i++) {
        res.append(data[i]);
        if (i !=size-1) // 最后一個元素不要加,
            res.append(", ");
    }
    res.append(']');
    return res.toString();
}

package cn.mtianyan;

public class ArrayTest {
public static void main(String[] args) {
Array array = new Array(20);
for (int i = 0; i < 10; i++) {
array.addLast(i);
}
System.out.println(array);
}
}

    array.add(1,100);
    System.out.println(array);
    array.addFirst(-1);
    System.out.println(array);

取出某一個元素。

/**
 * 傳入索引,獲取該位置元素
 *
 * @param index 要獲取的元素索引
 * @return 返回該位置數組元素
 */
public int get(int index){
    if (index<0 || index>=size){
        throw new IllegalArgumentException("Get failed. Required index<0 or index>=size ");
    }else {
        return data[index];
    }
}

通過get方法用戶無法使用到數組后半截沒有使用到的空間,通過封裝保證。

System.out.println(array.get(array.getSize()-1));

/**
 * 傳入索引和元素值,將該位置元素設置為傳入值
 * @param index 索引
 * @param e 傳入值
 */
public void set(int index,int e){
    if (index<0 || index>=size){
        throw new IllegalArgumentException("Set failed. Required index<0 or index>=size ");
    }else {
        data[index] = e;
    }
}
    array.set(0,-99);
    System.out.println(array);

數組中的包含,搜索和刪除元素

如果要刪除索引為1的元素,從88開始,后面都要往前挪,挪size-index次。只需要size--,對于最后一個位置的元素不用做其他操作,因為用戶也訪問不到。

/**

  • 查找數組中是否有元素e
  • @param e
  • @return 包含 true; 不包含 false
    */
    public boolean contains(int e){
    for (int i = 0; i < size; i++) {
    if (data[i] == e){
    return true;
    }
    }
    return false;
    }

    /**

  • 查找數組中元素,返回其索引(第一個遇到)
  • @param e 元素
  • @return 不存在 -1; 存在 i
    */
    public int find(int e){
    for (int i = 0; i < size; i++) {
    if (data[i] == e){
    return i;
    }
    }
    return -1;
    }

    public int[] findAll(int e){
    int[] tempArray=new int[size];
    int index = 0;
    for (int i = 0; i < size; i++) {
    if (data[i] == e){
    tempArray[index] = i;
    index++;
    }
    }
    int[] indexArray=new int[index];
    for (int i = 0; i < index; i++) {
    indexArray[i] = tempArray[i];
    }
    return indexArray;
    }
    注意findAll中使用tempArray臨時對象的作用: 同時將int數組,與它的size一起傳遞了出來。

    System.out.println("===============加入重復元素后數組如下===================");
    array.add(3,3);
    array.add(array.getSize()-1,9);
    System.out.println(array);
    System.out.println("================包含 尋找測試===========================");
    System.out.println(array.contains(-99));
    System.out.println(array.contains(-100));
    System.out.println("3的index: "+array.find(3));
    int [] tmpArr = array.findAll(3);
    for (int i : tmpArr) {
        System.out.print(i+" ");
    }
    System.out.println();

    /**

  • 刪除數組元素,返回刪除的元素值
  • @param index 索引
  • @return 該索引位置元素值
    */
    public int remove(int index){
    // 判空,空數組 removeFirst index=0,size=0,index>=size異常。空數組 removeLast index=-1 index<0異常;
    if (index<0 || index>=size){
    throw new IllegalArgumentException("Remove failed. Required index<0 or index>=size ");
    }else {
    int ret = data[index];
    for (int i=index+1;i < size;i++){
    // 從哪個元素開始挪,從index位置的后一個元素開始,挪到哪個元素結束,挪到size-1(因此沒等號)
    data[i-1] = data[i]; // 前一個等于后一個
    }
    size--;
    return ret;
    }
    }

    /**

  • 刪除第一個(索引0)元素
  • @return 刪除的元素值
    */
    public int removeFirst(){
    return remove(0);
    }

    /**

  • 刪除最后一個(索引size-1)元素
  • @return 刪除的元素值
    */
    public int removeLast(){
    return remove(size-1);
    }

    /**

  • 刪除數組中某一個元素值(刪除數組中第一個找到的)
  • @param e 元素值
  • @return 刪除是否成功
    */
    public boolean removeElement(int e){
    int index = find(e);
    if (index != -1){
    remove(index);
    return true;
    }
    return false;
    }

    /**

  • 刪除數組中包含的所有該元素值
  • @param e 元素值
  • @return 刪除成功與否
    */
    public boolean removeAllElement(int e){
    int[] indexArray = findAll(e);
    if (indexArray.length != 0){
    for (int i = 0; i < indexArray.length; i++) {
    remove(indexArray[i]-i); // 此處注意-i的巧妙
    }
    return true;
    }
    return false;
    }
    重難點代碼在于remove(indexArray[i]-i);此處很可能會被忽略,造成異常。因為數組每刪除一個,原本獲取到的臨近后一個元素的index值應該-1;臨近后兩個則應該-2;以此類推。

    System.out.println("================開始刪除測試========================");
    System.out.println(array);
    array.remove(3); // 刪除一個3
    System.out.println(array);
    array.removeElement(1); // 刪除1
    System.out.println(array);
    System.out.println("=====刪除index3 后 刪除元素1如上====");
    
    array.removeAllElement(9);
    System.out.println(array);
    System.out.println("=====刪除所有9=====");
    array.addFirst(-99);
    array.removeAllElement(-99);
    System.out.println(array);
    System.out.println("=====首位添加-99,后刪除所有-99 結果如上=====");
    
    array.add(4,99);
    array.add(5,99);
    array.addFirst(99);
    array.addLast(99);
    array.add(7,99);
    System.out.println(array);
    System.out.println("=====上面為刪除99前,下面為刪除99后=====");
    array.removeAllElement(99);
    System.out.println(array);

使用泛型

我們上面實現的數組目前只能存放int類型,但是我們需要的是可以承載多種類型,甚至自定義對象的容器。Java泛型可以解決這個問題。

使用泛型: 讓我們的數據結構可以放置“任何”數據類型,不可以是基本數據類型,只能是類對象。

java中的八種基本類型: boolean , byte, char , short , int , long , float , double;

每個基本數據類型都有對應的包裝類: Boolean , Byte , Char , Short, Int , Long , Float , Double。自動的拆包,解包。

public class Array<Element>
public class Array<E>
為類名后面添加泛型類型標識,此處的類型標識可以自定義。

private E[] data;

這里無法直接通過E實例化。只能通過間接的Object再轉換。

data = (E[]) new Object[capacity];
/**

  • 靜態數組入參構造函數
  • @param data 傳入靜態數組
    */
    public Array(E[] data) {
    this.data = data;
    }
    public void add(int index,E e){
    public void addLast(E e){
    public void addFirst(E e){
    public boolean contains(int E){
    for (int i = 0; i < size; i++) {
    if (data[i].equals(E)){
    return true;
    }
    }
    return false;
    }
    這里注意,兩個對象之間的比較要使用equals方法,將所有的與成員數組類型相關的都改了。

    public E remove(int index){
    // 判空,空數組 removeFirst index=0,size=0,index>=size異常。空數組 removeLast index=-1 index<0異常;
    if (index<0 || index>=size){
    throw new IllegalArgumentException("Remove failed. Required index<0 or index>=size ");
    }else {
    E ret = data[index];
    for (int i=index+1;i < size;i++){
    // 從哪個元素開始挪,從index位置的后一個元素開始,挪到哪個元素結束,挪到size-1(因此沒等號)
    data[i-1] = data[i]; // 前一個等于后一個
    }
    size--;
    data[size] = null;
    return ret;
    }
    }
    data[size]還指著一個值不過用戶訪問不到而已,新的元素添加會自動覆蓋。使用泛型后,data數組中存放的是類對象的引用, size-- 后data[size]依然指向一個引用,引用就涉及到空間釋放的問題,Java有自動垃圾回收機制,但如果 data[size]仍存放引用,就不會被自動垃圾回收。data[size] = null;(不必須,可以被新對象覆蓋)

如果不置為null,它會被稱為loitering Objects,沒有用但垃圾回收機制不回收。 loitering Objects != memory leak 為了程序優化,手動去除更好。

Main中改動

Array<Integer> array = new Array(20);
Array<Integer> array = new Array<>(20);
即使不改,也是可以正常運行的。但是我們最好加上更清楚的看出類型,jdk1.7之后不用再后面再重復一次。

package cn.mtianyan;

public class Student {

private String name;
private int score;

public Student(String studentName, int studentScore){
    name = studentName;
    score = studentScore;
}

@Override
public String toString(){
    return String.format("Student(name: %s, score: %d)", name, score);
}

public static void main(String[] args) {

    Array<Student> arr = new Array();
    arr.addLast(new Student("mtianyan1", 100));
    arr.addLast(new Student("mtianyan2", 66));
    arr.addLast(new Student("mtianyan3", 88));
    System.out.println(arr);
}

}

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Student)) return false;
    Student student = (Student) o;
    return score == student.score &&
            (name.equals(student.name));
}

@Override
public int hashCode() {
    return Objects.hash(name,score);
}

重寫hashCode和equals方法。

    System.out.println("================start=======================");
    System.out.println(arr);
    arr.remove(1);
    Student stu1 = new Student("mtianyan1", 100);
    Student stu3 = new Student("mtianyan3", 88);
    arr.removeElement(stu1);
    System.out.println(arr);
    System.out.println("==============================================");
    System.out.println(arr);
    arr.removeAllElement(stu3);
    System.out.println(arr);

動態數組

Java靜態數組容量有限,固定。我們要將其改造成一個可伸縮的。

不夠用了,就開辟一個新的數組,容量是原來的二倍(4變為8),然后把原來數組的內容進行復制過來(size不變),最后將data指向新的數組。這里要遍歷的把每個值都搞過去,對于性能消耗大不大呢,本章后兩節會討論。

    if (isFull())
        throw new IllegalArgumentException("AddLast failed. Array is Full");
    // 大于size就不是緊密排列了
    if (index<0 || index>size){
        throw new IllegalArgumentException("AddLast failed. Required index<0 or index>size ");
    }

坐標非法依然拋出異常,但是如果滿了,我們的動態數組將不會拋出異常。

    if (isFull())
        resize(2*data.length);

這里的2倍,是優于擴大固定大小值的。2的選擇是我們自己決定的,Collection中ArrayList中選取了1.5。

private void resize(int newCapacity) {
    E[] newData = (E[]) new Object[newCapacity];
    for (int i = 0; i < size; i++) {
        newData[i] = data[i];
    }
    data = newData;
}

package cn.mtianyan;

public class ArrayTestDynamic {
public static void main(String[] args) {
Array<Integer> array = new Array<>();
for (int i = 0; i < 10; i++) {
array.addLast(i);
}
System.out.println(array);
array.addFirst(99);
System.out.println(array);
array.addLast(100);
System.out.println(array);
}
}

刪除時壓縮數組容量

public E remove(int index){
    // 判空,空數組 removeFirst index=0,size=0,index>=size異常。空數組 removeLast index=-1 index<0異常;
    if (index<0 || index>=size){
        throw new IllegalArgumentException("Remove failed. Required index<0 or index>=size ");
    }else {
        E ret = data[index];
        for (int i=index+1;i < size;i++){
            // 從哪個元素開始挪,從index位置的后一個元素開始,挪到哪個元素結束,挪到size-1(因此沒等號)
            data[i-1] = data[i]; // 前一個等于后一個
        }
        size--;
        data[size] = null;

        if (size == data.length /2 )
            resize(data.length /2);
        return ret;
    }
}

這里設置一個閾值,當數組一半為空時,容量減半。

package cn.mtianyan;

public class ArrayTestDynamic {
public static void main(String[] args) {
Array<Integer> array = new Array<>();
for (int i = 0; i < 10; i++) {
array.addLast(i);
}
System.out.println(array);
array.addFirst(99);
System.out.println(array);
array.addLast(99);
System.out.println(array);
array.removeAllElement(99);
System.out.println(array);
for (int i = 0; i < 5; i++) {
array.removeElement(i);
}
System.out.println(array);
}
}

簡單的復雜度分析

簡單的時間復雜度分析 感性認識 考研: 細致推導

O(1), O(n) , O(lgn) , O(nlogn) , O(n^2);

大O描述的是算法的運行時間和輸入數據之間的關系

public static int sum(int[] nums){
int sum = 0;
for(int num: nums) sum += num;
return sum;
}
通常我們說上面這個代碼的算法,時間復雜度是O(n)的。n是nums中的元素個數;算法和n呈線性關系,線性關系表現在一次方。

num 數字越多,算法執行時間越長。

為什么要用大O,叫做O(n)?

因為我們忽略了常數,實際時間 T=c1*n + c2,大O是一個大概的; 對于每一個數所花的時間是c1,int return sum時間是c2。

因此,無論c1,c2是多少,只要符合線性關系都是O(n)的算法。

即使c1,c2很小,但是是二次方,因此是O(n2),性能差于O(n)。沒錯,理論性能差,但是對于一些小數據,如n=10,反倒是O(n2)的要執行更快。

漸進時間復雜度,描述n趨近于無窮的情況。(3000)高階算法常數低反而快于低階算法: 高級排序算法,歸并排序,快速排序,都可以對于小數據轉為插入排序,獲得10-15%優化,插入排序高階但常數小。

因為是趨向于無窮的情況,因此可以將其中的低階項忽略掉。

分析動態數組的時間復雜度

添加操作

addLast(e) // O(1) 末尾添加,直接賦值,其他什么也不做。
O(1)意味著它與我們的數組規模沒有關系,不管數組n有多大,addLast都能在常數時間內完成,此時不考慮size變化。

addFirst(e) // O(n) 前面添加,所有元素后挪
n個元素,就要后挪n-1次。

add(index, e)
與index有關。因此可以這樣分析:index這么多取值的概率是相等的,嚴格計算需要一些概率論知識,求出時間期望;平均來看是O(n/2)

添加操作綜合來看,整體是O(n)級別的。

在算法復雜度分析上通常關注的是最壞的情況,有一些特例,下小節講個特例。

resize // O(n)級別
刪除操作:

修改操作:

set(index, e) // O(1)級別
查找操作:

get(index) // O(1)
contains(e) // O(n)
find(e) // O(n)

數組最好情況是已知索引。

如果只對最后一個元素操作,addLast RemoveLast O(1)依然是O(n),因為resize把全部元素重新遍歷賦值一遍。看起來resize好像是一個性能很差的操作。

對于resize的分析,使用最差情況是不合理的。下一小節使用均攤復雜度進行分析。

均攤復雜度和防止復雜度的震蕩

添加操作

最壞情況下,尾部添加就擴容,從O(1)變成了O(n)級別;但是我們不可能每次操作都resize,10次才會觸發一次resize。

假設當前capacity=8,并且每一次添加操作都使用addLast:

9次addLast操作,觸發resize,總共進行了17次基本操作。大約平均每次addLast操作,進行2次基本操作。

推廣開: 假設capacity = n, 第n+1次addLast,觸發resize,總共進行2n+1次基本操作,平均每次addLast操作,進行2次基本操作。

這就將resize的時間平均給了每一次addLast操作,這樣均攤下來,時間復雜度是O(1)級別的!和n沒有關系。

在這個例子里,這樣均攤計算,比計算最壞情況有意義。均攤復雜度 amortized time complexity

同理,我們看removeL ast操作,均攤復雜度也為0(1)

復雜度震蕩。

我們單獨看removeLast和addLast,它的復雜度為O(1)

但當兩個在capacity臨界點,一次add 一次remove,會不停的觸發resize。存在該情況,出現問題的原因: removeLast時resize過于著急
(Eager)

解決方案: Lazy

當數組size是當前容量的1/4,進行縮容量,縮1/2,則還有1/4空間可以加元素。size == capacity/4時,才將capacity減半。

Lazy方式,懶一下,算法性能更好。后面講到線段樹時還有例子:當更懶,改善算法性能(懶機制代碼更多)。

        if (size == data.length /4 && data.length/2 !=0)
            resize(data.length /2);

因為是整數除法,所以當length為1時,會造成等于0,而我們無法new一個容量為0的數組。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

通许县| 周口市| 长子县| 翼城县| 鄂州市| 南岸区| 青海省| 定兴县| 泸西县| 依安县| 南和县| 丹江口市| 象山县| 壶关县| 靖边县| 安国市| 瓮安县| 财经| 福建省| 久治县| 滨州市| 工布江达县| 佛山市| 布尔津县| 东平县| 宜城市| 桃江县| 西林县| 江门市| 兴安县| 平阳县| 常熟市| 许昌县| 土默特左旗| 登封市| 武清区| 安顺市| 万盛区| 营口市| 潼南县| 松原市|