In many situations, the best way to store data is tables.
Array is the best choice when the data is densely clustered in a certain coordinate range.
For example, to store the scores of students in a class, there are 30 students in the class, numbered from 1-30, 10 courses, numbered from 1-10
Then you can use an array of 30x10 to store this score sheet, which is economical and convenient to use
But sometimes the data is very sparse. For example, a school has opened 1000 courses, and some students can choose courses at will. Now, a data structure is needed to store the scores of each student in each course.
Obviously, a student can only take more than ten courses a semester, so the data in the table is very sparse, most of the nodes are empty nodes, without data. If you use arrays, you need to allocate space in advance, not only the score table itself is very large, but also a lot of space is wasted.
At this point, you can use sparse tables.
Imagine now that there is a record of 3000 students in 1000 subjects.
Now we abstract every non empty table element into a node. Each column is strung up to form many columns, which are put into an array together. Each row is strung up to form many rows, which are put into an array together. In this way, the nodes selected from the whole dense array are weaved into a sparse net.
Just maintain and store this network
The following picture:
The attributes of the sparse table are as follows:
int rowTotalNum; / / total rows
int colTotalNum; / / total columns
LinkedList[] row; / / the pointer of the row list. The size should be the same as the following
LinkedList[] col; / / the pointer of the column list. The size should be the same as the following
The sparse table provides the following methods:
SparseTable(): constructor, initialization parameter
Void insert (int value, int rowIndex, int colIndex): inserts a value into a point (rowIndex, colIndex) of the sparse table
void remove(int rowIndex, int colIndex): delete the node of a coordinate (rowIndex, colIndex)
int getValue(int rowIndex, int colIndex): get the value of the specified coordinate (rowIndex, colIndex). If there is no such node, return - 1. There are two methods inside the method, by column and by row
void printSelfByRow(): print sparse table through row linked list
void printSelfByCol(): print sparse table through column linked list
LinkedList getCol(int colIndex): get the column list of a column
LinkedList getRow(int rowIndex): get the row list of a row
Here is the C + + Code:
#include<iostream> #include<string> #include<stdio.h> using namespace std; //Node class, representing the nodes of the linked list class Node{ public: int row; int col; int value; //Value of storage node Node* next; //Store next node's pointer Node(int aValue, int aRow, int aCol, Node* aNext = NULL){ //Constructor, the value of the node must be passed in, and the next node defaults to NULL this->value = aValue; this->next = aNext; this->row = aRow; this->col = aCol; } }; class LinkedList{ //Common unidirectional list class public: int length; //The length of the linked list is not important. It is not used in the following methods, but it is maintained Node* head; //Pointer to the chain header node Node* tail; //Pointer to the node at the end of the list LinkedList(){ length = 0; head = tail = 0; } //Treat the list as a list of columns, and insert the node at row row with value void insertToCol(int value, int row, int col){ //Search in col where nextCol > col //Find the node whose coordinate is row in a column, and return the value of the previous node of the node //The returned results are as follows //1. NULL: indicates that the node is found and it is the head node, so there is no precursor node; //2. NULL: the list is empty //3. It is not empty, indicating that the precursor node is found, and the basis is that the coordinates of the successor node are larger than the coordinates to be inserted Node* aheadOfInsert = searchInCol(row); //insertNode in that position //If the link list is empty and NULL is returned, the node needs to be inserted in the first position to update the head and tail if (aheadOfInsert == NULL && isEmpty()){ head = tail = new Node(value, row, col); } //If NULL is returned and the linked list is not empty, the first node should be added and the head should be updated else if (aheadOfInsert == NULL && !isEmpty()){ head = new Node(value, row, col, head); } //Otherwise, insert normally else { aheadOfInsert->next = new Node(value, row, col, aheadOfInsert->next); } length++; } //Similarly, insert a node into the col coordinate of the row list 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++; } //Take the current linked list as a column linked list, and delete the node whose coordinate is row void deleteByCol(int row, int col){ //Search in col where nextCol > col //Search for the precursor node to delete //The returned results are as follows //1. NULL: indicates that the node is found and it is the head node, so there is no precursor node; //2. NULL: the list is empty //3. NULL: the node was not found //4. It is not empty, indicating that the precursor node is found, and the basis for finding is that the coordinates of the successor node are larger than the coordinates to be inserted, so the successor node is not necessarily the node to be deleted Node* aheadOfDelete = searchInCol(row); Node* deletedNode=NULL; //Case 1: it may be necessary to delete the head node and compare the coordinates. If it is necessary to delete it, delete the head node and update the head if (aheadOfDelete == NULL && !isEmpty() && head->col==col && head->row==row){ deletedNode = head; head = head->next; length--; } //Cases 2 and 3: no node found to delete, return else if (aheadOfDelete==NULL) return ; //Case 3: no node found to delete, return else if(aheadOfDelete->next == NULL) return ; //Otherwise, delete the node normally else if (aheadOfDelete->next->col==col && aheadOfDelete->next->row==row){ deletedNode = aheadOfDelete->next; aheadOfDelete->next = aheadOfDelete->next->next; length--; } //Free the memory of this node delete deletedNode; } //Take the current linked list as a row linked list, and delete the node whose coordinate is col //Empathy 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; } //According to the column, find the value of the coordinate (row, col) int getValueByCol(int row, int col){ //Search in col where nextCol > col //Search for the precursor node to find //The returned results are as follows //1. NULL: this node is found and it is the head node, so there is no precursor node; //2. NULL: this node is not found, which means that the list is not empty but NULL is returned; //3. NULL: the list is empty //4. NULL: the node was not found //5. It is not empty, indicating that the precursor node is found, and the basis for finding is that the coordinates of the successor node are larger than the coordinates to be inserted, so the successor node is not necessarily the node to be deleted Node* aheadOfNode = searchInCol(row); //Situation 1 if (aheadOfNode == NULL && !isEmpty() && head->col==col && head->row==row){ return head->value; } //Situation 2 else if (aheadOfNode == NULL && !isEmpty()) return -1; //Situation 3 else if (aheadOfNode==NULL && isEmpty()) return -1; //Situation 4 else if(aheadOfNode->next == NULL) return -1; //Situation 5 else if (aheadOfNode->next->col==col && aheadOfNode->next->row==row){ return aheadOfNode->next->value; } return -1; } //Find the value of the coordinate (row, col) according to the row int getValueByRow(int row, int col){ //Search in col where nextCol > col //Search for the precursor node to find //The returned results are as follows //1. NULL: indicates that the node is found and it is the head node, so there is no precursor node; //2. NULL: the list is empty //3. NULL: the node was not found //4. It is not empty, indicating that the precursor node is found, and the basis for finding is that the coordinates of the successor node are larger than the coordinates to be inserted, so the successor node is not necessarily the node to be deleted Node* aheadOfNode = searchInRow(col); //Situation 1 if (aheadOfNode == NULL && !isEmpty() && head->col==col && head->row==row){ return head->value; } //Situation 2 else if (aheadOfNode == NULL && !isEmpty()) return -1; //Situation 3 else if (aheadOfNode==NULL && isEmpty()) return -1; //Situation 4 else if(aheadOfNode->next == NULL) return -1; //Situation 5 else if (aheadOfNode->next->col==col && aheadOfNode->next->row==row){ return aheadOfNode->next->value; } return -1; } //Take the current linked list as a column linked list and search the node whose row coordinate is row Node* searchInCol(int row){ //return NULL if list is empty; //If the link list is empty or the row coordinate of the head node is greater than the row coordinate of the node to be searched, return null if (isEmpty() || head->row>=row) return NULL; Node* now; //Loop lookup //There are two conditions for cycle termination: //1. At the end of the list, it is still not found. Corresponding to now - > next! = null //2. The row coordinate of the next node is smaller than the row coordinate to be searched, corresponding to now - > next - > row < row for (now=head; now->next!=NULL && now->next->row<row; now=now->next); return now; } //Empathy //Take the current linked list as a row linked list, and search for nodes whose column coordinates are 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(){ //Judge whether the chain list is empty or not. If the head pointer is 0, it means empty return head == 0; } void printSelf(){ //Print linked list content } }; //Sparse table class SparseTable{ public: int rowTotalNum; //Total number of banks int colTotalNum; //Total column number LinkedList* row[500]; //The pointer of the row list. The size should be the same as the following LinkedList* col[500]; //Pointer to the column list, the size should be consistent with the following //Construction method 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(); } //Insert value value into a point (rowIndex, colIndex) of sparse table void insert(int value ,int rowIndex, int colIndex){ if (rowIndex >= rowTotalNum || colIndex >= colTotalNum) return ; if(getValue(rowIndex, colIndex) != -1) return ; //Insert nodes into row list and column list respectively row[rowIndex]->insertToRow(value, rowIndex, colIndex); col[colIndex]->insertToCol(value, rowIndex, colIndex); } //Delete a node of a coordinate (rowIndex, colIndex) void remove(int rowIndex, int colIndex){ if (rowIndex >= rowTotalNum || colIndex >= colTotalNum) return ; //Delete nodes on row and column lists row[rowIndex]->deleteByRow(rowIndex, colIndex); col[colIndex]->deleteByCol(rowIndex, colIndex); } //Get the value of the specified coordinate (rowIndex, colIndex) //If there is no such node, return - 1 //There are two ways to do this, by column and by row 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); } //Print sparse table through row linked list 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; } } //Printing sparse tables with column linked lists 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; } } //Get column list of a column LinkedList* getCol(int colIndex){ return col[colIndex]; } //Get row list of a row LinkedList* getRow(int rowIndex){ return row[rowIndex]; } }; //Test program 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; }