###C/C++泛型编程实现数据结构之单链表
###线性表的链式存储结构
线性表的顺序存储结构的特点是:在逻辑上关系相邻的元素在物理上的位置也是相邻的,因此顺序存储结构的线性表随机存储的时间复杂度为O(1),因为CPU不比花费过多的时间在内存寻址中,数据排列在内存中是紧凑的。但是正因如此,导致顺序存储的插入和删除操作需要一一移动元素来实现,最坏情况下的时间复杂度可以达到O(N^2)
当经常需要做插入和删除操作的运算时,链式存储结构可以避免这些移动由于链式存储结构存储线性表的空间有可能时连续的,也有可能是不连续的,所以基于链式存储结构存储的线性表不具有随机存储的能力
####链式存储结构时最常用存储方式之一,不仅可以用来表示线性表,而且还可以用来表示各种非线性数据结构。
##有关操作系统的底层问题
在操作系统执行时,每个程序都在各自的虚拟内存中运行,而操作系统则维护一张内存的映射表。
使用顺序存储结构时,随着空间不停的被分配和删除,空闲空间逐渐被分割成很小的部分,最终导致出现存储碎片,虽然这个时候空间的总空闲数比申请的要多,但是因为不连续,所以无法使用。解决上述问题的方法就是采用不连续空间分配,也就是链式存储。
链式存储的实质就是为每个数据构造所使用内存块的链表。使用这种结构就会将了逻辑上连续的文件分散在若干不连续的物理块中。使用这种结构有利于数据的动态扩充,有利于数据的插入和删除,并且提高了内存空间的使用率。
动态扩充时只要从内存空间中得到一个空闲空间,并且将其链接到原来的链表尾部,就可以改变原链表长度。
但是链式存储的存取速度慢,不适合随机存取数据,存在数据可靠性问题,指针出错了,数据也就出错了,另外连接指针也需要占领一部分的空间。
同理,在访问一个链表的时候,CPU也需要花费更多的寻址时间和寻址次数。
##代码部分刨析
####建表
建表方法分为头插法和尾插法,这里我们对尾插法还做了头节点上的优化。由于时间问题,这里就只贴出单链表的实现代码。
头插法建表:这里需要先传入一个节点,由于头插法的节点时插入在头节点后,所以第一个节点的Next应该为NULL,否则遍历链表的时候会出错,没有停止条件。
//头插法建表
ListNode * Create_List_head() {
DataType ch;
ch = getchar();
head = NULL;
p = (ListNode*)malloc(sizeof(ListNode));
p->data = ch;
p->next = NULL;
head = p;
ch = getchar();
while (ch != '\n') {
p = (ListNode*)malloc(sizeof(ListNode));
p->data = ch;
p->next = head;
head = p;
ch = getchar();
}
return head;
}
尾插法建表:这里并没有引入头节点的概念,只是将头节点作为一个指针,并让头节点直接指向第一个插入的元素。
//尾插法建表
ListNode * Create_List_rear() {
DataType ch;
head = NULL;
rear = NULL;
ch = getchar();
while (ch != '\n'){
p = (ListNode*)malloc(sizeof(ListNode));
p->data = ch;
if (head == NULL)head = p;
else rear->next = p;
rear = p;
ch = getchar();
}
if (rear != NULL)rear->next = NULL;
return head;
}
尾插法建表优化代码:引入了头节点的概念,并减少了算法的时间复杂度。让尾指针首先指向头指针,然后尾指针指向插入的节点。保持尾指针始终在尾部。
//尾插法引入头节点后简化算法
ListNode* Create_List_rear_greater() {
head = (ListNode*)malloc(sizeof(ListNode));
DataType ch;
rear = head;
while ((ch = getchar()) != '\n') {
p = (ListNode*)malloc(sizeof(ListNode));
p->data = ch;
rear->next = p;
rear = p;
}
rear->next = NULL;
return head;
}
链表中的查找操作,得到链表的第i个节点,这里要注意的是p = head->next这句,需要判断一下链表有没有头节点的概念,如果没有代码应该改成p = head
//链表中的查找运算,得到链表中的第i个节点
ListNode* GetNodei(int i){
p = head->next;
int j = 1;
while (p != NULL && j < i) {
p = p->next;
++j;
}
if (j == i) { return p; }
else return NULL;
}
//链表中按节点的值查找节点所在位置
int LocateNode_Value(DataType Value) {
p = head->next;
int i = 1;
while (p != NULL && p->data != Value) {
p = p->next;
++i;
}
if (p->data == Value) { return i; }
else return -1;
}
//链表中按节点值查找节点
ListNode* LocateNode_Value_P(DataType Value){
p = head->next;
while (p != NULL && p->data != Value) {
p = p->next;
}
if (p->data == Value) { return p; }
else return NULL;
}
链表中的插入和删除运算,这里我们将会很明显的看出这个时间复杂度为O(1),在插入和删除过程中最主要的操作就是查找待插入点和查找待删除点的操作
//链表中在第i个节点的位置插入一个值为Value的节点
bool InsertLisk(int i, DataType Value)
{
int j = 0;
//根据插入方法选择是否需要改为 p = head->next;这个地方可以选择重载函数
p = head;
while (p != NULL && j < i - 1) {
p = p->next; ++j;
}
if (p == NULL)return false;
else {
ListNode* s = (ListNode*)malloc(sizeof(ListNode));
s->data = Value;
s->next = p->next;
p->next = s;
}
}
//删除链表中第i个节点位置上的值,并返回该值
DataType DeleteList(int i) {
int j = 0;
p = head;
DataType data;
while (p != NULL && j < i-1)
{
p = p->next; ++j;
}
if (p == NULL) { cout << "List ERROR,there is nothing to Delete!"; exit(0); }
else {
ListNode* s = (ListNode*)malloc(sizeof(ListNode));
s = p -> next;
p->next = s->next;
data = s->data;
free(s);
return data;
}
}
###完整代码,类模板
现在我将给出类模板的完整代码,有兴趣的可以自己实现一下双向链表和循环链表的类模板,这在工作和实践中是至关重要的。
template <typename DataType>
class LinkList {
private:
typedef struct node {
DataType data;
node* next;
}ListNode;
protected:
ListNode* p;
ListNode* head;
ListNode* rear;
public:
//头插法建表
ListNode * Create_List_head() {
DataType ch;
ch = getchar();
head = NULL;
p = (ListNode*)malloc(sizeof(ListNode));
p->data = ch;
p->next = NULL;
head = p;
ch = getchar();
while (ch != '\n') {
p = (ListNode*)malloc(sizeof(ListNode));
p->data = ch;
p->next = head;
head = p;
ch = getchar();
}
return head;
}
//尾插法建表
ListNode * Create_List_rear() {
DataType ch;
head = NULL;
rear = NULL;
ch = getchar();
while (ch != '\n'){
p = (ListNode*)malloc(sizeof(ListNode));
p->data = ch;
if (head == NULL)head = p;
else rear->next = p;
rear = p;
ch = getchar();
}
if (rear != NULL)rear->next = NULL;
return head;
}
//尾插法引入头节点后简化算法
ListNode* Create_List_rear_greater() {
head = (ListNode*)malloc(sizeof(ListNode));
DataType ch;
rear = head;
while ((ch = getchar()) != '\n') {
p = (ListNode*)malloc(sizeof(ListNode));
p->data = ch;
rear->next = p;
rear = p;
}
rear->next = NULL;
return head;
}
//链表中的查找运算,得到链表中的第i个节点
ListNode* GetNodei(int i){
p = head->next;
int j = 1;
while (p != NULL && j < i) {
p = p->next;
++j;
}
if (j == i) { return p; }
else return NULL;
}
//链表中按节点的值查找节点所在位置
int LocateNode_Value(DataType Value) {
p = head->next;
int i = 1;
while (p != NULL && p->data != Value) {
p = p->next;
++i;
}
if (p->data == Value) { return i; }
else return -1;
}
//链表中按节点值查找节点
ListNode* LocateNode_Value_P(DataType Value){
p = head->next;
while (p != NULL && p->data != Value) {
p = p->next;
}
if (p->data == Value) { return p; }
else return NULL;
}
//链表中在第i个节点的位置插入一个值为Value的节点
bool InsertLisk(int i, DataType Value)
{
int j = 0;
//根据插入方法选择是否需要改为 p = head->next;这个地方可以选择重载函数
p = head;
while (p != NULL && j < i - 1) {
p = p->next; ++j;
}
if (p == NULL)return false;
else {
ListNode* s = (ListNode*)malloc(sizeof(ListNode));
s->data = Value;
s->next = p->next;
p->next = s;
}
}
//删除链表中第i个节点位置上的值,并返回该值
DataType DeleteList(int i) {
int j = 0;
p = head;
DataType data;
while (p != NULL && j < i-1)
{
p = p->next; ++j;
}
if (p == NULL) { cout << "List ERROR,there is nothing to Delete!"; exit(0); }
else {
ListNode* s = (ListNode*)malloc(sizeof(ListNode));
s = p -> next;
p->next = s->next;
data = s->data;
free(s);
return data;
}
}
};