1. 程式人生 > 實用技巧 >資料結構與演算法-向量

資料結構與演算法-向量

向量

介面與實現

ADT介面

資料結構 = 基於某種特定語言,實現ADT的一整套演算法

作為一種抽象資料型別,向量物件支援以下介面:

操作例項

模板類

*
     * 向量模板
     *0009 typedef int Rank; //秩
     * 0010 #define DEFAULT_CAPACITY  3 //預設的初始容量(實際應用中可設定為更大)
     * 0011
     * 0012 template <typename T> class Vector { //向量模板類
     * 0013 protected:
     * 0014    Rank _size; int _capacity;  T* _elem; //規模、容量、資料區
     * 0015    void copyFrom ( T const* A, Rank lo, Rank hi ); //複製陣列區間A[lo, hi)
     * 0016    void expand(); //空間不足時擴容
     * 0017    void shrink(); //裝填因子過小時壓縮
     * 0018    bool bubble ( Rank lo, Rank hi ); //掃描交換
     * 0019    void bubbleSort ( Rank lo, Rank hi ); //起泡排序演算法
     * 0020    Rank max ( Rank lo, Rank hi ); //選取最大元素
     * 0021    void selectionSort ( Rank lo, Rank hi ); //選擇排序演算法
     * 0022    void merge ( Rank lo, Rank mi, Rank hi ); //歸併演算法
     * 0023    void mergeSort ( Rank lo, Rank hi ); //歸併排序演算法
     * 0024    void heapSort ( Rank lo, Rank hi ); //堆排序(稍後結合完全堆講解)
     * 0025    Rank partition ( Rank lo, Rank hi ); //軸點構造演算法
     * 0026    void quickSort ( Rank lo, Rank hi ); //快速排序演算法
     * 0027    void shellSort ( Rank lo, Rank hi ); //希爾排序演算法
     * 0028 public:
     * 0029 // 建構函式
     * 0030    Vector ( int c = DEFAULT_CAPACITY, int s = 0, T v = 0 ) //容量為c、規模為s、所有元素初始為v
     * 0031    { _elem = new T[_capacity = c]; for ( _size = 0; _size < s; _elem[_size++] = v ); } //s<=c
     * 0032    Vector ( T const* A, Rank n ) { copyFrom ( A, 0, n ); } //陣列整體複製
     * 0033    Vector ( T const* A, Rank lo, Rank hi ) { copyFrom ( A, lo, hi ); } //區間
     * 0034    Vector ( Vector<T> const& V ) { copyFrom ( V._elem, 0, V._size ); } //向量整體複製
     * 0035    Vector ( Vector<T> const& V, Rank lo, Rank hi ) { copyFrom ( V._elem, lo, hi ); } //區間
     * 0036 // 解構函式
     * 0037    ~Vector() { delete [] _elem; } //釋放內部空間
     * 0038 // 只讀訪問介面
     * 0039    Rank size() const { return _size; } //規模
     * 0040    bool empty() const { return !_size; } //判空
     * 0041    Rank find ( T const& e ) const { return find ( e, 0, _size ); } //無序向量整體查詢
     * 0042    Rank find ( T const& e, Rank lo, Rank hi ) const; //無序向量區間查詢
     * 0043    Rank search ( T const& e ) const //有序向量整體查詢
     * 0044    { return ( 0 >= _size ) ? -1 : search ( e, 0, _size ); }
     * 0045    Rank search ( T const& e, Rank lo, Rank hi ) const; //有序向量區間查詢
     * 0046 // 可寫訪問介面
     * 0047    T& operator[] ( Rank r ); //過載下標操作符,可以類似於陣列形式引用各元素
     * 0048    const T& operator[] ( Rank r ) const; //僅限於做右值的過載版本
     * 0049    Vector<T> & operator= ( Vector<T> const& ); //過載賦值操作符,以便直接克隆向量
     * 0050    T remove ( Rank r ); //刪除秩為r的元素
     * 0051    int remove ( Rank lo, Rank hi ); //刪除秩在區間[lo, hi)之內的元素
     * 0052    Rank insert ( Rank r, T const& e ); //插入元素
     * 0053    Rank insert ( T const& e ) { return insert ( _size, e ); } //預設作為末元素插入
     * 0054    void sort ( Rank lo, Rank hi ); //對[lo, hi)排序
     * 0055    void sort() { sort ( 0, _size ); } //整體排序
     * 0056    void unsort ( Rank lo, Rank hi ); //對[lo, hi)置亂
     * 0057    void unsort() { unsort ( 0, _size ); } //整體置亂
     * 0058    int deduplicate(); //無序去重
     * 0059    int uniquify(); //有序去重
     * 0060 // 遍歷
     * 0061    void traverse ( void (* ) ( T& ) ); //遍歷(使用函式指標,只讀或區域性性修改)
     * 0062    template <typename VST> void traverse ( VST& ); //遍歷(使用函式物件,可全域性性修改)
     * 0063 }; //Vector
     *
     *

構造方法

public void copyFrom(Collection<T> A, int lo, int hi){//泛型:https://www.cnblogs.com/coprince/p/8603492.html
        T[] _elem = new T[2 * (hi - lo)];//分配空間
        int _size = 0;
        while(lo < hi){
            _elem[_size++] = A[lo++];
        }
    }
    *
     * public Vector(Collection<? extends E> c) {
     * //將集合Collection轉化為Object陣列elementData。如果c為空,那麼就會報空指標異常
     * 	elementData = c.toArray();
     * 	elementCount = elementData.length;
     * 	//將c中的元素拷貝到elementData中
     * 	if (elementData.getClass() != Object[].class)
     * 		elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
     * }

擴充與縮減

對於靜態空間,在新增元素時會出現上溢和下溢,因此需要對空間進行動態管理

擴容

容量遞增的策略:每次增加為原來的兩倍(只是表示意思,下面程式碼並沒有全寫上,可能是錯的)


//二倍增容
public void expand(int _size,int _capacity,int DEFAULT_CAPACITY,Collection<T> oldElem){
        if (_size < _capacity){
            return;
        }
        _capacity = Math.max(_capacity,DEFAULT_CAPACITY);
        T[] _elem = new T[_capacity <<= 1];
        for (int i = 0; i < _size;i++){
            _elem[i] = oldElem[i];
        }
    }
  /**vertor原始碼上的
   * 增加此向量的容量(如有必要),以確保其至少能夠儲存最小容量引數指定的元件數。
   * 如果當前陣列的容量小於minCapacity,那麼就增加容量,增加陣列長度
   * 新陣列的長度等於原陣列的長度加上增量capacityIncrement。
   * 如果增加capacityIncrement小於等於0,那麼就自動擴增為原來二倍。
   * 如果擴增為原來的二倍還是比minCapacity小,那麼就將minCapacity作為Object陣列的長度。
   */
public synchronized void ensureCapacity(int minCapacity) {
        if (minCapacity > 0) {
            modCount++;
            ensureCapacityHelper(minCapacity);
        }
    }

    private void ensureCapacityHelper(int minCapacity) {
        //minCapacity為實際向量中的元素需要的容量,如果minCapacity大於當前陣列長度,那麼就進行擴容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    private void grow(int minCapacity) {
        //oldCapacity舊容量是Object陣列的長度
        int oldCapacity = elementData.length;
    //如果增量capacityIncrement大於0,那麼新容量為舊容量加上增量,否則為舊容量的2倍
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                capacityIncrement : oldCapacity);
    //如果新容量小於實際需要的容量,就將實際需要的容量賦值給新容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
    //新容量比陣列的最大容量還大,就進行擴容為最大容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
    //原先的資料域中逐一取出各項轉移到新的資料域中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
    //如果實際需要的容量小於0就丟擲異常
        if (minCapacity < 0)
            throw new OutOfMemoryError();
    //實際容量如果比最大容量還大,那麼實際容量為Integer.MAX_VALUE,否則為Integer.MAX_VALUE - 8
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

縮容

@Test
public void shrink(int _size,int _capacity,int DEFAULT_CAPACITY,Collection<T> oldElem){
    if (_capacity < DEFAULT_CAPACITY << 1){//不致收縮到DEFAULT_CAPACITY以下
        return;
    }
    if (_capacity < _size << 2){//以25%為界
        return;
    }
    T[] _elem = new T[_capacity <<= 1];
    for (int i = 0; i < _size;i++){
        _elem[i] = oldElem[i];
    }
}

致亂演算法

@Test
public void permute(Collection<T> A){
    for (int i = A.size();i > 0;i--){
        int math = (int)(Math.random()*A.size());
        int swap = A[i - 1];
        A[i - 1] = A[math];
        A[math] = swap;
    }
}

無序向量

元素及相關操作

  • 訪問
  • 區間刪除
public static int[] remove(int[] arr,int lo,int hi) { //刪除陣列中某一元素方法
    while(hi < arr.length - 1){
        arr[++lo] = arr[++hi];
    }
    arr = Arrays.copyOf(arr, arr.length-(hi - lo)); //減小陣列長度
    return arr;
}

無序向量的查詢

public int find(Object e , int lo, int hi, Object[] _elem){
    while(( lo < hi--)&&(e != _elem[hi]));
    return hi;//若hi < lo,則意味著失敗;否則hi即命中元素的秩
}
public int remove(int lo, int hi, Object[] _elem){
    if ( lo == hi){
        return 0;
    }
    while( hi < _elem.length){
        _elem[lo++] = _elem[hi++];
    }
    //shrink();
    return hi - lo;
}

無序向量的唯一化

public int deduplicate(int _size ,Object[] _elem){
    int oldsize = _size;
    int i = 1;
    while(i < _size){
        (find(_elem[i],0,i,_elem)) ? i++ : remove(i);
    }
    return  oldsize - _size;
}

遍歷

@Test
public void test3(){
    Vector<String> t=new Vector<String>();
    t.add("F");
    t.add("o");
    t.add("r");
    t.add("e");
    t.add("v");
    t.add("e");
    t.add("r");
    //第一種
    for (String string : t) {
        System.err.print(string);
    }
    //第二種
    t.forEach(new Consumer<String>() {
        @Override
        public void accept(String t) {
            // TODO Auto-generated method stub
            System.out.print(t);
        }
    });
    //第三種
    for (int i = 0; i < t.size(); i++) {
        System.out.print(t.get(i));
    }
    //第四種
    Iterator<String> it = t.iterator();
    while (it.hasNext()) {
        String string = (String) it.next();
        System.err.print(string);
    }
    //第五種
    Enumeration<String> enume = t.elements();
    while(enume.hasMoreElements()){
        System.out.print(enume.nextElement().toString());
    }

}

有序向量:唯一性

有序程度

@Test
public int test1(){
    int[] _elem = new int[]{1,5,6,9,12,63,2,49,31,67};
    int n = 0;
    for (int i = 1;i < _elem.length;i++){
        if (_elem[i -1] > _elem[i]){
            n++;
        }
    }
    return n;
}

低效版

@Test
public void test2(){
    int[] _elem = new int[]{1,5,5,5,6,9,9,9,31,49,49,52,63};
    int oldsize = _elem.length;
    int i = 0;
    while(i < _elem.length - 1){
        if (_elem[i] == _elem[i+1]){
            _elem = remove(_elem,i,i+1);
        }else {
            i++;
        }
    }
    System.out.println(Arrays.toString(_elem));
    System.out.println(oldsize - _elem.length);
}

public static int[] remove(int[] arr,int lo,int hi) { //刪除陣列中某一元素方法
    while(hi < arr.length - 1){
        arr[++lo] = arr[++hi];
    }
    arr = Arrays.copyOf(arr, arr.length-(hi - lo)); //減小陣列長度
    return arr;
}

高效辦


@Test
public void test3(){
    int[] _elem = new int[]{1,5,5,5,6,9,9,9,31,49,49,52,63};
    int i = 0;
    int j = 0;
    int oldsize = _elem.length;
    while (++j < _elem.length){
        if (_elem[i] != _elem[j]){
            _elem[++i] = _elem[j];
        }
    }
    _elem = remove(_elem, i,oldsize - 1);
    System.out.println(Arrays.toString(_elem));
    System.out.println(oldsize - 1 -i);
}

有序向量:查詢

二分法:簡單版

@Test
public void test1(){
    int[] _elem = new int[]{1,5,5,5,6,9,9,9,31,49,49,52,63};
    int lo = 0;
    int e = 32;
    int hi = _elem.length;
    int mi = find3(_elem,e,lo,hi);
    System.out.println(mi);

}

//簡單的二分法查詢
public int find1(int[] S,int e,int lo,int hi){
    while (lo < hi){
        int mi = (lo + hi) >>1;
        if (e < S[mi]){
            hi = mi;
        }else if (S[mi] < e){
            lo = mi + 1;
        }else {
            return mi;
        }
    }
    return -1;
}

二分法:不考慮軸點


/**
 * 二分法查詢:不算軸點
 */
public int find3(int[] S,int e,int lo,int hi){
    while (lo < hi){
        int mi = (lo + hi) >>1;
        if (e < S[mi]){
            hi = mi;
        }else {
            lo = mi +1;
        }
    }
    if(S[--lo] == e){

        return --lo;
    }else {
        return -1;
    }
}

斐波那契查詢


/**
 * 斐波那契數列查詢
 *  向量的長度 n = fib(k) - 1,則可取mi = fib(k - 1) - 1,於是,前、後子向量的長度分別為
 *  fib(k-1)-1、 fib(k-2)-1;這裡並不要求向量的長度一定要正好==fib(n) - 1;只要
 *  用最小的n滿足size<=fib(n) - 1即可
 */

public int find2(int[] S,int e,int lo,int hi){
    while (lo < hi){
        int n = 0;
        while (hi - lo > fib(n) - 1){
            n++;
        }
        int mi = lo + fib(n-1) - 1;
        if (e < S[mi]){
            hi = mi;

        }else if (S[mi] < e){
            lo = mi + 1;
        }else {
            return mi;
        }
    }
    return -1;
}
public int fib(int n){
    int f = 0;
    int g = 1;
    while(0 < n--){
        g = g + f;
        f = g - f;
    }
    return g;
}

插值查詢

不一定每次都固定的選取 mi 相對於 lo 和 hi 的值,而是可以根據具體值來動態的確定 mi 。
二分查詢 是對 n 的數值每次折半的話,那 插值查詢 實際上是對 n 的二進位制位寬度來做二分查詢。二分查詢的迭代次數,我們知道是 logn 的,而 長度是 logn 的,所以最後插值查詢的迭代次數就是 loglogn 的。

public int find4(int[] S,int e,int lo,int hi){
    while (lo < hi){
        int mi = lo + (hi - lo)*(e - S[lo]) / (S[hi] - S[lo]);
        if (e < S[mi]){
            hi = mi;
        }else if (S[mi] < e){
            lo = mi + 1;
        }else {
            return mi;
        }
    }
    return -1;
}

排序

起泡排序

  • 簡單版-考慮是否已經全部有序
@Test
public void test1(){
    int[] _elem = {100, 836, 3236, 5, 16, 26, -3, 89, 69, 43};
    int n = _elem.length;
    sort4(_elem,0,n);
    System.out.println(Arrays.toString(_elem));
}

/**
 * 排列的時候考慮到過程中已經全部有序的情況,這裡是n--
 */
public void sort1(int[] A,int n) {
    for (boolean sorted = false; sorted = !sorted; n--) {
        for (int i = 1; i < n; i++) {
            if (A[i - 1] > A[i]) {
                int b = A[i - 1];
                A[i - 1] = A[i];
                A[i] = b;
                sorted = false;
            }
        }
    }
}
  • 改進1-考慮是否後方區域性有序

/**
 * 排列的時候針對亂序元素位於[0,根號n],考慮到過程中後方已經有序的情況,這裡是n-很多
 */
public void sort2(int[] A,int lo,int hi) {
    while (lo < (hi = bubble1(A,lo,hi)));
}
public int bubble1(int[] A,int lo,int hi){//一趟掃描
    int last = lo;
    while (++lo < hi){
        if (A[lo - 1] > A[lo]) {
            last = lo;
            int b = A[lo - 1];
            A[lo - 1] = A[lo];
            A[lo] = b;
        }
    }
    return last;
}
  • 改進2-考慮是否前方區域性有序
/**
 * 排列的時候針對亂序元素位於[n-根號n,n],考慮到過程中前方已經有序的情況,這裡是n-很多
 */
public void sort3(int[] A,int lo,int hi) {
    while ((lo = bubble2(A,lo,hi)) < hi );
}
public int bubble2(int[] A,int lo,int hi){//一趟掃描
    int first = hi;
    while (lo < --hi){
        if (A[hi - 1] > A[hi]) {
            first = hi - 1;
            int b = A[hi - 1];
            A[hi - 1] = A[hi];
            A[hi] = b;
        }
    }
    return first;
}
  • 改進3-考慮前後不斷排除有序情況
public void sort4(int[] A,int lo,int hi) {
    while ((lo = bubble2(A,lo,hi)) < (hi = bubble1(A,lo,hi)) );
} 

選擇排序

public class choiceSort {
    /**
     * 選擇排序
     */
    @Test
    public void test2(){
        int [] arr = {49,38,65,97,76,13,27,49};
        selectSort(arr,arr.length);
    }

    public void selectSort(int [] arr,int n){
        for (int i = 0; i < n - 1; i++) {
            int index = i;
            int j;
            // 找出最小值得元素下標
            for (j = i + 1; j < n; j++) {
                if (arr[j] < arr[index]) {
                    index = j;
                }
            }
            int tmp = arr[index];
            arr[index] = arr[i];
            arr[i] = tmp;
        }

    }
}

歸併排序




public class MergeSort {
    public static void main(String []args){
        int[] arr = {100, 836, 3236, 5, 16, 26, -3, 89, 69, 43};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void sort(int []arr){
        int []temp = new int[arr.length];//在排序前,先建好一個長度等於原陣列長度的臨時陣列,避免遞迴中頻繁開闢空間
        sort(arr,0,arr.length-1,temp);
    }
    private static void sort(int[] arr,int left,int right,int []temp){
        if(left<right){
            int mid = (left+right)/2;
            sort(arr,left,mid,temp);//左邊歸併排序,使得左子序列有序
            sort(arr,mid+1,right,temp);//右邊歸併排序,使得右子序列有序
            merge(arr,left,mid,right,temp);//將兩個有序子數組合並操作
        }
    }
    private static void merge(int[] arr,int left,int mid,int right,int[] temp){
        int i = left;//左序列指標
        int j = mid+1;//右序列指標
        int t = 0;//臨時陣列指標
        while (i<=mid && j<=right){
            if(arr[i]<=arr[j]){
                temp[t++] = arr[i++];
            }else {
                temp[t++] = arr[j++];
            }
        }
        while(i<=mid){//將左邊剩餘元素填充進temp中
            temp[t++] = arr[i++];
        }
        while(j<=right){//將右序列剩餘元素填充進temp中
            temp[t++] = arr[j++];
        }
        t = 0;
        //將temp中的元素全部拷貝到原陣列中
        while(left <= right){
            arr[left++] = temp[t++];
        }
    }
}