資料結構與演算法—稀疏陣列和佇列

丁可樂發表於2019-07-15

稀疏陣列和佇列

1.稀疏陣列

所謂稀疏陣列就是當陣列中大部分的內容值都未被使用(或都為零),在陣列中僅有少部分的空間使用。因此造成記憶體空間的浪費,為了節省記憶體空間,並且不影響陣列中原有的內容值,我們可以使用稀疏陣列去壓縮資料。OK,如果你不明白,那我們來看一個例子。

 

在一個五子棋中,有存檔和續上盤的功能

資料結構與演算法—稀疏陣列和佇列

分析問題:因為該二維陣列的很多預設值是 0,因此記錄了很多沒有意義的資料 > 稀疏陣列

 

1.1 解決方法

思路

  • 記錄陣列一共有幾行幾列,有多少個不同的值

  • 把具有不同值的元素的行列及值記錄在一個小規模的陣列中,從而縮小程式的範圍

     

資料結構與演算法—稀疏陣列和佇列

應用例項

  • 使用稀疏陣列,來保留類似前面的二維陣列(棋盤、地圖等等)
  • 把稀疏陣列存檔,並且可以從新恢復為原來的二維陣列
  • 整體思路
    資料結構與演算法—稀疏陣列和佇列

 

1.2 程式碼實現

public class SparseArray {
    public static void main(String[] args) {
        //建立一個二維陣列
        //0:表示沒有棋子 1表示黑子 2表示藍子
        int chessArr[][] = new int[11][10];
        chessArr[1][2] = 1;
        chessArr[2][3] = 2;
        for(int[] row:chessArr){
            for(int data:row){
                System.out.printf("%d\t",data);
            }
            System.out.println();
        }
        
        int[][] array = getSparseArray(chessArr);
        System.out.println("-------");
        for(int i = 0 ; i< array.length;i++){
            System.out.printf("%d\t%d\t%d\t\n",array[i][0],array[i][1],array[i][2]);
        }
        System.out.println("--------");
        int[][] startArr = recovery(array);
        for(int[] row:startArr){
            for(int data:row){
                System.out.printf("%d\t",data);
            }
            System.out.println();
        }
    }
    
    /**
     * 將普通陣列轉換為稀疏陣列
     * @param chessArr
     * @return 
     */
    public static int[][] getSparseArray(int[][] chessArr){
        if(!checkIsRight(chessArr)){
            return null;
        }
        
        
        //1.拿到陣列後 首先獲取元素的個數,然後才能建立稀疏陣列
        int sum = 0;
        for(int[] arr:chessArr){
            for(int i:arr){
                if(i != 0){
                    sum++;
                }
            }
        }
        
        //2.建立稀疏陣列
        int[][] sparseArr = new int[sum+1][3];
        sparseArr[0][0] = chessArr.length; //行
        sparseArr[0][1] = chessArr[0].length;//列
        sparseArr[0][2] = sum; //元素個數
        
        //3.陣列存放
        int count = 0;
        for(int i = 0; i <chessArr.length; i++ ){
            for(int j = 0; j <chessArr[i].length;j++ ){
                if(chessArr[i][j] != 0){
                    sparseArr[++count][0] = i;//行
                    sparseArr[count][1] = j;//列
                    sparseArr[count][2] = chessArr[i][j];
                }
            }
        }
        
        return sparseArr;
    }
    
    /**
     * 將稀疏陣列轉回普通陣列
     * @param sparseArr
     * @return
     */
    public static int[][] recovery(int[][] sparseArr){
        if(!checkIsRight(sparseArr)){
            return null;
        }   
        
        //獲取原陣列的 行數和列數 並建立原陣列
        int arr[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
        
        for(int i = 1; i < sparseArr.length;i++){
            arr[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
        }
        
        return arr;
    }
    
    
    public static boolean checkIsRight(int[][] arr){
        if(arr == null || arr.length <= 1 ){
            return false;
        }
        return true;
    }
    
}

 

2. 佇列

  • 佇列是一個有序列表,可以用陣列或連結串列來實現
  • 遵循先入先出的原則

資料結構與演算法—稀疏陣列和佇列

 

2.1 陣列模擬佇列

  • 佇列本身是有序列表,若使用陣列的資料結構來儲存佇列的資料,則佇列的陣列宣告如上圖,其中maxSize是該佇列的最大容量
  • 因為佇列的輸出、輸入分別從頭尾端來處理,因此需要兩個變數front及rear分別記錄佇列頭尾端的下標,front會隨著資料輸出而改變,而rear會隨著佇列的輸入而改變
  • 當我們將資料輸入佇列時稱為 addQueueaddQueue的處理有兩個步驟:思路分析

(1) 將尾指標往後移:rear+1,當front == rear [空]

(2) 若尾指標rear小於佇列的最大下標 maxSize - 1,則資料輸入rear 所指的陣列元素中,否則無法存入資料。

rear == maxSize - 1

程式碼實現

public class ArrayQueueDemo {
    public static void main(String[] args) {
        ArrayQueue queue = new ArrayQueue(10);
        queue.addQueue(1);
        queue.addQueue(2);
        queue.addQueue(3);
        queue.addQueue(4);
        queue.getQueue();
        queue.showQueue();
    }
}
//使用陣列模擬佇列-編寫一個ArrayQueue類
class ArrayQueue{
    private int maxSize; //表示陣列的最大容量
    private int front;//佇列頭
    private int rear;//佇列尾
    private int[] arr; //該陣列用於存放資料,模擬佇列
    
    public ArrayQueue(int maxSize){
        this.maxSize = maxSize;
        arr = new int[maxSize];
        this.front = -1;
        this.rear = -1;
    }
    
    //判斷佇列是否已滿
    public boolean isFull(){
        return rear == maxSize - 1;
    }
    
    //判斷佇列是否為空
    public boolean isEmpty(){
        return front == rear;
    }
    
    //新增資料到佇列
    public void addQueue(int n){
        //判斷佇列是否滿
        if(isFull()){
            System.out.println("佇列已滿");
            return;
        }
        
        arr[++rear] = n;
    }
    
    //獲取佇列的資料,出佇列
    public int getQueue(){
        if(isEmpty()){
            throw new RuntimeException("佇列已空");
        }
        return arr[++front];
    }
    
    //顯示佇列所有資料
    public void showQueue(){
        if(isEmpty()){
            System.out.println("佇列已空");
            return;
        }
        
        for(int i = front+1 ;i <= rear;i++){
            System.out.printf("arr[%d]=%d\n",i,arr[i]);
        }
    }
}

 

2.2 陣列模擬環形佇列

之前實現的佇列存在一個明顯的問題,就是陣列使用一次就不能再用了,出佇列資料的位置始終空在那,沒有達到一個複用的效果,因此我們要對這個佇列進行一次優化,將此佇列變成一個環形佇列

思路

  1. front 變數的含義做一個調整:front就指向佇列的第一個元素,也就是 arr[front] 就代表佇列的第一個元素,
    front初始值 = 0

  2. rear 的變數含義做一個調整:rear指向最後一個元素的後一個位置,因為希望空出一個空間作為約定,rear的初始值 = 0

  3. 當佇列滿時,條件是 (rear + 1) % maxSize == front 【滿】

  4. 當佇列為空的條件,rear == front 空

  5. 當我們這樣分析,佇列中有效的資料的個數 (rear + maxSize - front) % maxSize

  6. 我們就可以在原來的佇列上修改得到 一個環形佇列

 

程式碼實現

public class CircleArrayQueueDemo {
    public static void main(String[] args) {
        //測試一把
        System.out.println("測試陣列模擬環形佇列的案例");
        
        //建立一個環形佇列 說明設定4,其佇列資料最大是3
        CircleArray queue = new CircleArray(4);
        char key = ' ';//接收使用者輸入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        //輸出一個選單
        while(loop){
            System.out.println("s(show):顯示佇列");
            System.out.println("e(exit):退出程式");
            System.out.println("a(add):新增資料到佇列");
            System.out.println("g(get):從佇列取出資料");
            System.out.println("h(head):檢視佇列頭的資料");
            key = scanner.next().charAt(0);
            switch (key) {
            case 's':
                queue.showQueue();
                break;
            case 'a':
                System.out.println("輸出一個數字");
                int value = scanner.nextInt();
                queue.addQueue(value);
                break;
            case 'g':
                try {
                    int res = queue.getQueue();
                    System.out.printf("取出的資料是%d\n",res);
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
                break;
            case 'h'://檢視佇列頭的資料
                try {
                    int res = queue.headQueue();
                    System.out.printf("佇列頭的資料是%d\n",res);
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
                break;
            case 'e'://退出
                scanner.close();
                loop = false;
                break;
            default:
                break;
            }
        }
    }
}
class CircleArray{
    private int maxSize;
    private int front;
    private int rear;
    private int[] arr;
    
    public CircleArray(int maxSize){
        this.maxSize = maxSize;
        arr = new int[maxSize];
    }
    
    //判斷佇列是否已滿
    public boolean isFull(){
        return (rear+1)%maxSize == front;
    }
    
    //判斷佇列是否為空
    public boolean isEmpty(){
        return rear == front;
    }
    
    //新增資料到佇列
    public void addQueue(int n){
        //判斷佇列是否已滿
        if(isFull()){
            System.out.println("佇列滿,不能加入資料");
            return;
        }
        //直接將資料加入
        arr[rear] = n;
        //將rear後移,這裡必須考慮取模
        rear = (rear+1)%maxSize;
    }
    
    //獲取佇列的資料
    public int getQueue(){
        //判斷佇列是否為空
        if(isEmpty()){
            //通過丟擲異常
            throw new RuntimeException("佇列為空,不能取資料");
        }
        int value = arr[front];
        front = (front+1)%maxSize;
        return value;
    }
    
    //顯示佇列的所有資料
    public void showQueue(){
        //遍歷
        if(isEmpty()){
            System.out.println("佇列為空,沒有資料");
            return;
        }
        
        for(int i = front; i < front + size() ; i++){
            System.out.printf("arr[%d]=%d\n",i%maxSize,arr[i%maxSize]);
        }
        
    }
    
    //求出當前佇列有效資料的個數
    public int size(){
        //加上maxSize 防止模出負數 因為這是一個環形佇列
        return (rear + maxSize - front)%maxSize;
    }
    
    //顯示佇列的頭資料
    public int headQueue(){
        //判斷
        if(isEmpty()){
            throw new RuntimeException("佇列是空的,~沒有資料");
        }
        return arr[front];
    }
    
    
}

相關文章