算法與數據結構【Java】:稀疏表

很多情境下,存儲數據的最好方式就是表。

 
當數據較爲稠密的聚集在某個座標範圍中時,採用數組是最好的選擇。
比如,要存儲一個班學生的成績,該班級有30人,編號從1-30,有10門課,編號1-10
那麼就可以用一個30x10的數組存儲這張成績表,經濟實惠使用方便

但是有的時候數據非常稀疏,比如一個學校一共開設了1000門課,有的學生可以隨意選課,現在需要一個數據結構存儲每個學生每門課的成績。
顯然一個學生一學期只能上十幾門課,所以表內的數據很稀疏,大部分結點都是空結點,沒有數據。如果使用數組,那就需要提前分配好空間,不僅成績表本身很大,也浪費了很多空間。

這個時候,就可以使用稀疏表。


想象現在有一張記錄3000名學生在1000門課的成績。
現在把每個非空的表中元素抽象成一個結點,每一列串起來形成很多列,這些列一起放入一個數組;每一行串起來形成很多行,這些行一起放入一個數組,這樣,整個稠密的數組被選出來的結點編織成一張稀疏的網。
維護與存儲這張網即可

如下圖:

 

稀疏表的屬性如下:


    int rowTotalNum;    //總行數
    int colTotalNum;    //總列數
    LinkedList[] row;    //行鏈表的指針,大小需要與下方一致
    LinkedList[] col;    //列鏈表的指針,大小需要與下方一致

稀疏表提供的方法如下:


    SparseTable():構造方法,初始化參數
    void insert(int value ,int rowIndex, int colIndex):向稀疏表的某個點(rowIndex,colIndex)插入value值
    void remove(int rowIndex, int colIndex):刪除某個座標(rowIndex,colIndex)的結點
    int getValue(int rowIndex, int colIndex):獲取指定座標(rowIndex,colIndex)的值,如果沒有該結點,返回-1;方法內部提供了兩種方法,按列查找和按行查找
    void printSelfByRow():通過行鏈表打印稀疏表
    void printSelfByCol():通過列鏈表打印稀疏表

    LinkedList getCol(int colIndex):獲取某列的列鏈表
    LinkedList getRow(int rowIndex):獲取某行的行鏈表

下方是Java實現:

package com.sparsetable;

public class SparseTable {
	int rowTotalNum;	//總行數
	int colTotalNum;	//總列數
	LinkedList[] row;	//行鏈表的指針,大小需要與下方一致
	LinkedList[] col;	//列鏈表的指針,大小需要與下方一致
	
	//構造方法
	SparseTable(){
		rowTotalNum = 500;
		colTotalNum = 500;
		row = new LinkedList[rowTotalNum];
		col = new LinkedList[colTotalNum];
		for (int i=0; i<rowTotalNum; i++)
			row[i] = new LinkedList();
		for (int i=0; i<colTotalNum; i++)
			col[i] = new LinkedList();
	}
	//向稀疏表的某個點(rowIndex,colIndex)插入value值
	void insert(int value ,int rowIndex, int colIndex){
		if (rowIndex >= rowTotalNum || colIndex >= colTotalNum)
			return ;
		if(getValue(rowIndex, colIndex) != -1)
			return ; 
		//向行鏈表和列鏈表分別插入結點
		row[rowIndex].insertToRow(value, rowIndex, colIndex);
		col[colIndex].insertToCol(value, rowIndex, colIndex);
	}
	
	//刪除某個座標(rowIndex,colIndex)的結點
	void remove(int rowIndex, int colIndex){
		if (rowIndex >= rowTotalNum || colIndex >= colTotalNum)
			return ;
		//刪除行鏈表和列鏈表上的結點
		row[rowIndex].deleteByRow(rowIndex, colIndex);
		col[colIndex].deleteByCol(rowIndex, colIndex);
	}

	//獲取指定座標(rowIndex,colIndex)的值
	//如果沒有該結點,返回-1
	//這裏提供了兩種方法,按列查找和按行查找
	int getValue(int rowIndex, int colIndex){
		if (rowIndex >= rowTotalNum || colIndex >= colTotalNum)
			return -1;
		//return col[colIndex].getValueByCol(rowIndex, colIndex);
		return row[rowIndex].getValueByRow(rowIndex, colIndex);
	}

	//通過行鏈表打印稀疏表
	void printSelfByRow(){
		System.out.println("By row--------------------------");
		for (int i=0;i<=10;i++){
			for(Node now=row[i].head; now!=null; now=now.next){
				System.out.print("(" + now.row + ", " + now.col + "): " + now.value + " ");
			}
			System.out.println();
		}
	}

	//通過列鏈表打印稀疏表
	void printSelfByCol(){
		System.out.println("By row--------------------------");
		for (int i=0;i<=5;i++){
			for(Node now=col[i].head; now!=null; now=now.next){
				System.out.print("(" + now.row + ", " + now.col + "): " + now.value + " ");
			}
			System.out.println();
		}
		
	}

	//獲取某列的列鏈表
	LinkedList getCol(int colIndex){
		return col[colIndex];
	}
	//獲取某行的行鏈表
	LinkedList getRow(int rowIndex){
		return row[rowIndex];
	}
	
	static public void main(String[] argv) {
		SparseTable st = new SparseTable();
		st.insert(1,1,2);
		st.insert(2,1,3);
		
		st.insert(3,2,4);
		st.insert(4,3,3);
		st.insert(44,3,5);
		st.insert(42,3,2);
		st.insert(42,3,2);
		st.printSelfByRow();
		st.printSelfByCol();
		
		
		st.remove(2,4);
		st.remove(2,4);
		st.printSelfByRow();
		st.printSelfByCol();

		st.remove(1,2);
		st.printSelfByRow();
		st.printSelfByCol();

		System.out.println("Get value: (3,5)" + st.getValue(3,5));
		System.out.println("Get value: (3,4)" + st.getValue(3,4));
		System.out.println("Get value: (3,2)" + st.getValue(3,2));
		
		
		
	}
	
	
}

class Node{
		int row;	
		int col;
		int value;	//存儲節點的值 
		Node next;	//存儲下一個節點的指針 
		Node(int aValue, int aRow, int aCol){	//構造函數,必須傳入結點的值,下一個節點默認爲null
			this.value = aValue;
			this.next = null;
			this.row = aRow;
			this.col = aCol;
		}	
		Node(int aValue, int aRow, int aCol, Node aNext){	//構造函數,必須傳入結點的值,下一個節點默認爲null
			this.value = aValue;
			this.next = aNext;
			this.row = aRow;
			this.col = aCol;
		}	
};


class LinkedList{	//普通單向鏈表類
		int length;		//鏈表長度,該屬性不重要,下面的方法中也沒有用到,但是維護了該屬性
		Node head;		//鏈表頭節點的指針
		Node tail;		//鏈表尾節點的指針
	LinkedList(){
		length = 0;
		head = tail = null;
	}

	//把鏈表當作一列的鏈表,在第row行插入結點,值爲value
	void insertToCol(int value, int row, int col){
		//Search in col where nextCol > col
		//在一列中查找座標爲row的結點,返回該結點的前一個結點的值
		//返回結果有如下情況
			//1、null:說明找到了該結點,且該結點是頭結點,所以沒有前驅結點;
			//2、null:鏈表爲空
			//3、不爲空,說明找到了前驅結點,尋找的依據是後繼結點的座標大於要插入的座標
		Node aheadOfInsert = searchInCol(row); 
		//insertNode in that position

		//如果鏈表爲空,返回了null,說明需要在第一個位置插入結點,更新head和tail
		if (aheadOfInsert == null && isEmpty()==1){
			head = tail = new Node(value, row, col);	
		}
		//如果返回null且鏈表不爲空,說明要添加首結點,更新head
		else if (aheadOfInsert == null && isEmpty()==0){
			head = new Node(value, row, col, head);
		}
		//否則,正常插入
		else {
			aheadOfInsert.next = new Node(value, row, col, aheadOfInsert.next);
		}
		length++;
	}
	
	//同理,向行鏈表的col座標處,插入結點
	void insertToRow(int value, int row, int col){
		//Search in col where nextCol > col
		Node aheadOfInsert = searchInRow(col); 
		//insertNode in that position
		if (aheadOfInsert == null && isEmpty()==1){
			head = tail = new Node(value, row, col);	
		}
		else if (aheadOfInsert == null && isEmpty()==0){
			head = new Node(value, row, col, head);
		}
		else {
			aheadOfInsert.next = new Node(value, row, col, aheadOfInsert.next);
		}
		length++;
	}
	

	//把當前鏈表當作列鏈表,刪除座標爲row的結點
	void deleteByCol(int row, int col){
		//Search in col where nextCol > col
		//搜索得到要刪除結點的前驅結點
		//返回結果有如下情況
			//1、null:說明找到了該結點,且該結點是頭結點,所以沒有前驅結點;
			//2、null:鏈表爲空
			//3、null:沒有找到該結點
			//4、不爲空,說明找到了前驅結點,尋找的依據是後繼結點的座標大於要插入的座標,所以後繼結點並不一定是要刪除的結點
		Node aheadOfDelete = searchInCol(row);
		
		Node deletedNode=null;
		//情況1:可能要刪除頭結點,對比座標,如果的確要刪除,刪除頭結點並更新head
		if (aheadOfDelete == null && isEmpty()==0
			&& head.col==col && head.row==row){
			deletedNode = head;
			head = head.next;
			length--;
		}
		//情況2、3:沒有找到要刪除的結點,返回
		else if (aheadOfDelete==null)
			return ;
		//情況3:沒有找到要刪除的結點,返回
		else if(aheadOfDelete.next == null)
			return ;
		//否則,正常刪除結點
		else if (aheadOfDelete.next.col==col && aheadOfDelete.next.row==row){
			deletedNode = aheadOfDelete.next;
			aheadOfDelete.next = aheadOfDelete.next.next;
			length--;
		}
	}
	
	//把當前鏈表當作行鏈表,刪除座標爲col的結點
	//同理
	void deleteByRow(int row, int col){
		//Search in col where nextCol > col
		Node aheadOfDelete = searchInRow(col);
		
		
		Node deletedNode=null;
		if (aheadOfDelete == null && isEmpty()==0 
			&& head.col==col && head.row==row){
			
			deletedNode = head;
			head = head.next;
			length--;
		}
		else if (aheadOfDelete==null)
			return ;
			
		else if(aheadOfDelete.next == null)
			return ;
		else if (aheadOfDelete.next.col==col && aheadOfDelete.next.row==row){
			deletedNode = aheadOfDelete.next;
			aheadOfDelete.next = aheadOfDelete.next.next;
			length--;
		}
		
	}
	
	//根據列,查找座標(row,col)的值
	int getValueByCol(int row, int col){
		//Search in col where nextCol > col
		//搜索得到要查找結點的前驅結點
		//返回結果有如下情況
			//1、null:找到了該結點,且該結點是頭結點,所以沒有前驅結點;
			//2、null:沒有找到該結點,表現爲鏈表不爲空但是返回了null;
			//3、null:鏈表爲空
			//4、null:沒有找到該結點
			//5、不爲空,說明找到了前驅結點,尋找的依據是後繼結點的座標大於要插入的座標,所以後繼結點並不一定是要刪除的結點
		
		Node aheadOfNode = searchInCol(row);
		
		//情況1
		if (aheadOfNode == null && isEmpty()==0
			&& head.col==col && head.row==row){
			return head.value;
		}
		//情況2
		else if (aheadOfNode == null && isEmpty()==0)
			return -1;
		//情況3
		else if (aheadOfNode==null && isEmpty()==1)
			return -1;
		//情況4
		else if(aheadOfNode.next == null)
			return -1;
		//情況5
		else if (aheadOfNode.next.col==col && aheadOfNode.next.row==row){
			return aheadOfNode.next.value;
		}
		return -1;
	}

	//根據行,查找座標(row,col)的值
	int getValueByRow(int row, int col){
		//Search in col where nextCol > col
		//搜索得到要查找結點的前驅結點
		//返回結果有如下情況
			//1、null:說明找到了該結點,且該結點是頭結點,所以沒有前驅結點;
			//2、null:鏈表爲空
			//3、null:沒有找到該結點
			//4、不爲空,說明找到了前驅結點,尋找的依據是後繼結點的座標大於要插入的座標,所以後繼結點並不一定是要刪除的結點
		
		Node aheadOfNode = searchInRow(col);
		
		//情況1
		if (aheadOfNode == null && isEmpty()==0
			&& head.col==col && head.row==row){
			return head.value;
		}
		//情況2
		else if (aheadOfNode == null && isEmpty()==0)
			return -1;
		//情況3
		else if (aheadOfNode==null && isEmpty()==1)
			return -1;
		//情況4
		else if(aheadOfNode.next == null)
			return -1;
		//情況5
		else if (aheadOfNode.next.col==col && aheadOfNode.next.row==row){
			return aheadOfNode.next.value;
		}
		return -1;
	}
	
	
	//把當前鏈表當作列鏈表,搜索行座標爲row的結點
	Node searchInCol(int row){
		//return null if list is empty; 
		//如果鏈表爲空或者頭節點的row座標已經大於要查找的結點row座標,返回空
		if (isEmpty()==1 || head.row>=row)
			return null;
		Node now;
		//循環查找
		//循環終止的條件有兩個:
			//1、到達鏈表尾,仍然沒有找到。對應now.next!=null
			//2、下一個結點的row座標已經小於要查找的row座標,對應now.next.row<row
		for (now=head; now.next!=null && now.next.row<row; now=now.next);
		return now;
	}

	//同理
	//把當前鏈表當作行鏈表,搜索列座標爲row的結點
	Node searchInRow(int col){
		//return null if list is empty; 
		if (isEmpty()==1 || head.col>=col)
			return null;
		Node now;
		for (now=head; now.next!=null && now.next.col<col; now=now.next);
		return now;
	}

	
	
	int isEmpty(){			//判斷鏈表是否爲空,頭指針爲0代表空
		return head == null?1:0;
	}	
	
	void printSelf(){	//打印鏈表內容	
		
	}
	
};

 

發佈了86 篇原創文章 · 獲贊 59 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章