算法与数据结构【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 篇原创文章 · 获赞 56 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/WilliamCode/article/details/104087981