很多情境下,存儲數據的最好方式就是表。
當數據較爲稠密的聚集在某個座標範圍中時,採用數組是最好的選擇。
比如,要存儲一個班學生的成績,該班級有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):獲取某行的行鏈表
下方是C++代碼:
#include<iostream>
#include<string>
#include<stdio.h>
using namespace std;
//節點類,代表鏈表的結點
class Node{
public:
int row;
int col;
int value; //存儲節點的值
Node* next; //存儲下一個節點的指針
Node(int aValue, int aRow, int aCol, Node* aNext = NULL){ //構造函數,必須傳入結點的值,下一個節點默認爲NULL
this->value = aValue;
this->next = aNext;
this->row = aRow;
this->col = aCol;
}
};
class LinkedList{ //普通單向鏈表類
public:
int length; //鏈表長度,該屬性不重要,下面的方法中也沒有用到,但是維護了該屬性
Node* head; //鏈表頭節點的指針
Node* tail; //鏈表尾節點的指針
LinkedList(){
length = 0;
head = tail = 0;
}
//把鏈表當作一列的鏈表,在第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()){
head = tail = new Node(value, row, col);
}
//如果返回NULL且鏈表不爲空,說明要添加首結點,更新head
else if (aheadOfInsert == NULL && !isEmpty()){
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()){
head = tail = new Node(value, row, col);
}
else if (aheadOfInsert == NULL && !isEmpty()){
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()
&& 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--;
}
//釋放該結點的內存
delete deletedNode;
}
//把當前鏈表當作行鏈表,刪除座標爲col的結點
//同理
void deleteByRow(int row, int col){
//Search in col where nextCol > col
Node* aheadOfDelete = searchInRow(col);
//cout<<"deleting by row"<<endl;
Node* deletedNode=NULL;
if (aheadOfDelete == NULL && !isEmpty()
&& 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--;
}
delete deletedNode;
}
//根據列,查找座標(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()
&& head->col==col && head->row==row){
return head->value;
}
//情況2
else if (aheadOfNode == NULL && !isEmpty())
return -1;
//情況3
else if (aheadOfNode==NULL && isEmpty())
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()
&& head->col==col && head->row==row){
return head->value;
}
//情況2
else if (aheadOfNode == NULL && !isEmpty())
return -1;
//情況3
else if (aheadOfNode==NULL && isEmpty())
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() || 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() || 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 == 0;
}
void printSelf(){ //打印鏈表內容
}
};
//稀疏表
class SparseTable{
public:
int rowTotalNum; //總行數
int colTotalNum; //總列數
LinkedList* row[500]; //行鏈表的指針,大小需要與下方一致
LinkedList* col[500]; //列鏈表的指針,大小需要與下方一致
//構造方法
SparseTable(){
rowTotalNum = 500;
colTotalNum = 500;
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(){
cout<<"By row--------------------------"<<endl;
for (int i=0;i<=10;i++){
for(Node* now=row[i]->head; now!=NULL; now=now->next){
cout<<"("<<now->row<<", "<<now->col<<"): "<<now->value<<" ";
}
cout<<endl;
}
}
//通過列鏈表打印稀疏表
void printSelfByCol(){
cout<<"By col--------------------------"<<endl;
for (int i=0;i<=5;i++){
for(Node* now=col[i]->head; now!=NULL; now=now->next){
cout<<"("<<now->row<<", "<<now->col<<"): "<<now->value<<" ";
}
cout<<endl;
}
}
//獲取某列的列鏈表
LinkedList* getCol(int colIndex){
return col[colIndex];
}
//獲取某行的行鏈表
LinkedList* getRow(int rowIndex){
return row[rowIndex];
}
};
//測試程序
int main(){
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();
cout<<"Get value: (3,5)"<<st->getValue(3,5)<<endl;
cout<<"Get value: (3,4)"<<st->getValue(3,4)<<endl;
cout<<"Get value: (3,2)"<<st->getValue(3,2)<<endl;
}