链表是什么???
链表是一种区别于顺序储存的非连续、非顺序的物理储存结构,也就是说,不像队列、堆栈之类的逻辑数据结构,它只是一种数据储存的物理结构。我们所使用的数组便是一种物理储存结构。
但与数组不同的是,它是链式储存的,而数组采用了顺序储存。
那么,什么是顺序储存与链式储存呢(⊙o⊙)?
顺序储存:把逻辑上相邻的结点存储在物理位置上相邻的存储单元,即每个结点之间的地址是连续的。
链式储存:用一组任意的储存单元存储线性表的数组元素,结点之间的地址不一定是连续的,但每个结点之间一定有着某种先后关系。
我们使用链式储存,可以更方便地插入、删除、更新元素,但是频繁查询元素时较耗时间。同时,链表由于多了指针域,更耗空间,却又避免了数组的大幅空间浪费的情况。
链表代码之指针实现:
//链表(包括有关链表的各种操作) #include<cstdio> #include<cstdlib> using namespace std; typedef struct List *Link;//Link代表链表指针 typedef struct List Lnode;//Lnode代表链表结点 struct List { int data; Link next; }; Link create(Link Head)//建表操作并返回头指针 { int newdata; Link newnode; Head=(Link)malloc(sizeof(Lnode)); printf("Please input number:\n"); scanf("%d",&newdata); Head->data=newdata; Head->next=NULL; while(1) { newnode=(Link)malloc(sizeof(Lnode)); if(newnode==NULL)//若空间分配失败 ,则终止循环 { printf("Sorry, it failed!"); break; } printf("Please input number : intput '-1' means exit\n");//输入链表元素,输入-1代表输入结束 scanf("%d",&newdata); if(newdata==-1) return Head; newnode->data=newdata; newnode->next=Head; Head=newnode; } return Head; } void dispaly(Link Head)//链表元素的显示 { Link p=Head; if(p==NULL)//链表为空 printf("\nList is empty\n"); else do { printf("%d",p->data); p=p->next; }while(p!=NULL); return; } Link insert(Link Head, int value, int i)//Head为头指针,value为插入值,i为插入结点的编号 { Link newnode,p=Head; int newdata,j=1; newnode=(Link)malloc(sizeof(Lnode)); newnode->data=value; if(i==1) { newnode->next=Head; Head=newnode; } else { while(j<i-1&&p->next!=NULL)//找到 i 结点的前一个元素 { p=p->next; j++; } if(j==i-1) { newnode->next=p->next; p->next=newnode; } else printf("Insert is Failure,i is not right!\n"); } return Head; } Link del(Link Head, int i)//Head为头指针,i为删除结点编号 { Link p=Head,t; int j=1; if(i==1) { Head=Head->next; } else { while(j<i-1&&p->next!=NULL) { p=p->next; j++; } if(j==i-1&&p->next!=NULL) { t=p->next; p->next=t->next; } if(t!=NULL) free(t);//释放内存,尽可能地节省空间 } return Head; } int get(Link Head, int i)//取编号为i的结点的值 { int j=1; Link p; p=Head; while(j<i&&p!=NULL) { p=p->next; j++; } if(p!=NULL) return p->data; else printf("Data is error!\n"); return -1; } int locate(Link Head, int x)//查找第一个值为x的结点的编号,若失败,则返回-1 { int n=1; Link p=Head; while(p!=NULL&&p->data!=x) { p=p->next; n++; } if(p!=NULL) return n; else printf("Not found!\n"); return -1; } int lengh(Link Head)//取链表长度 { int len=0; Link p=Head; while(p!=NULL) { len++; p=p->next; } return len; } Link connect(Link Head1, Link Head2)//以前面的链表为头,连接后续链表 { Link p=Head1; while(p->next!=NULL) { p=p->next; } p->next=Head2; return Head1; } bool compare(Link Head1, Link Head2)//比较两个链表是否相同 { Link p1,p2; p1=Head1; p2=Head2; while(1) { if(p1->next==NULL&&p2->next==NULL) return 1; else if(p1->data!=p2->data) return 0; else { p1=p1->next; p2=p2->next; } } } Link Set_NULL(Link Head)//清空链表 { Link p; p=Head; while(p!=NULL) { p=p->next; free(Head); Head=p; } return Head; } int main() { //链表的相关操作已给出 //以下省略一千字...... }
但实际上,我们同样可以用数组来模拟链表,而且对其操作的速度将大大提高(我也是在一位神犇那儿知道的),因为CPU缓存对连续的数据结构速度更快(貌似是因为储存结构层次之类乱七八糟的东西)!
因此,下面给出数组模拟链表的代码。
链表代码之数组实现:
//数组仿真链表的演示程序 #include<iostream> #include<cstdlib> #define MAXN 100001//链表中最多能容纳的元素数+1 using namespace std; int linklst_data[MAXN] ;//linklst_data 为链表中元素的数据 int linklst_point[MAXN] ;//linklst_point 为链表中元素的指针 int head = -1;/*链表的头指针*/ void del_by_data(int del_data)//删除一个含有del_data的元素 { int p=head,pre=-1;//从头开始遍历链表 while(p != -1) { if(linklst_data[p] == del_data)//如找到了要删除的值,则进行删除操作 { if(p == head)//如果删除的是头指针,则更新头指针 head = linklst_point[head]; else//这个else 等同于 if (pre != 1)因为当p!=head时p前面已经有元素了 linklst_point[pre] = linklst_point[p]; linklst_data[p] = -1;//删除p指向的元素的值*/ linklst_point[p] = -1; return ;/*结束删除操作*/ } pre = p; p = linklst_point[p]; } } void add_front(int add_data)//在链表前段加入元素 { int p = 1; while(linklst_data[p] != -1 && p < MAXN) //找一空位存储数据,这里可以优化 ++p; linklst_data[p] = add_data; //将要加入的节点选好的空位赋值 linklst_point[p] = head; //将当前加入元素的指针指向head head = p; //使当前的元素成为链表头指针 } void add_rear(int add_data)//在链表末尾加入元素 { int p = 1,pre; while(linklst_data[p] != -1 && p < MAXN )//找到空位 ++p; linklst_data[p] = add_data; //该空位赋值 if( head != -1 ) //链表不为空 { pre = head;//找到链表中的最后一个元素*/ while(linklst_point[pre] != -1)//找到空指针 pre = linklst_point[pre]; linklst_point[pre] = p;//将当前链表中最后一个元素的指针指向要加入的元素 } else//否则直接对head所指的元素赋值 head = p; return ; } void output() { int p=head; cout<<"list is: "; while(p != -1) { cout<<linklst_data[p]<<" "; p = linklst_point[p]; } cout<<"\n"; } void init()//初始化数组指针值 { for(int i = 0;i < MAXN;i++)//链表中的空值设定为-1 { linklst_point[i] = -1; linklst_data[i] = -1; } } int main()//演示链表的操作 { int ins,data; init(); while(1) { cout<<"1.insert a value in front \n"; cout<<"2.insert a value in rear \n"; cout<<"3.delete a value \n"; cout<<"4.quit \n"; cin>>ins; switch(ins) { case 1: cout<<"please insert a value :"; cin>>data; add_front(data); break; case 2: cout<<"please insert a value :"; cin>>data; add_rear(data); break; case 3: cout<<"please insert a value :"; cin>>data; del_by_data(data); break; default: return 0; } system("cls");//清空屏幕,此命令只能在window平台下使用 output(); } return 0; }
从代码中可以发现,每次进行插入操作时,都要在数组中找一空位进行操作,这就导致每次插入操作的时间复杂度都是线性的,导致很大的时间浪费。
因此,我们可以通过堆栈记录没有被加入链表的元素的下标,每次插入操作只需从栈中获取下标即可。这样,O(n)的线性查找便转化为了O(1)的出栈操作,时间上大大优化。
链表代码之数组实现(堆栈优化):
//数组链表的演示程序(栈优化) #include<iostream> #include<cstdlib> #define MAXN 100001 using namespace std; int linklst_data[MAXN] ; int linklst_point[MAXN] ; int stack[MAXN];//用于记录没有加入链表的数组下标的栈 int head = -1;//链表的头指针 int stack_head = 0;//栈顶指针 void del_by_data(int del_data)//删除一个含有del_data的元素 { int p=head,pre=-1; //从头开始遍历链表 while(p != -1) { if(linklst_data[p] == del_data)//找到要删除的值,进行删除操作 { if(p == head)//如果删除的是头指针,则更新头指针 head = linklst_point[head]; else//这个else 等同于 if (pre != 1)因为当p!=head时p前面已经有元素了 linklst_point[pre] = linklst_point[p]; linklst_data[p] = -1;//删除p指向的元素的值 linklst_point[p] = -1; stack[--stack_head] = p;//将删除元素的数组下标入栈 return ; } pre = p; p = linklst_point[p]; } return ; } void add_front(int add_data)//在链表前段加入元素*/ { int p = 1; p = stack[stack_head++];//直接取出栈中的空位*/ linklst_data[p] = add_data;//将要加入的节点选好的空位赋值*/ linklst_point[p] = head; //将当前加入元素的指针指向head*/ head = p; //使当前的元素成为链表头指针*/ } void add_rear(int add_data)//在链表末尾加入元素*/ { int p = 1,pre; p = stack[stack_head++];//直接取出栈中的空位*/ linklst_data[p] = add_data; //对要加入的节点的空位进行赋值*/ if( head != -1 ) //链表不为空 { pre = head;//找到链表中的最后一个元素*/ while(linklst_point[pre] != -1) pre = linklst_point[pre]; linklst_point[pre] = p;//将当前链表中最后一个元素的指针指向要加入的元素 } else//否则直接对head所指的元素赋值 head = p; } void output() { int p=head; cout<<"List: "; while(p != -1) { cout<<linklst_data[p]<<" "; p = linklst_point[p]; } cout<<"\n"; return ; } void init()//初始化数组指针值 { for(int i = 0;i < MAXN;i++) { linklst_point[i] = -1; linklst_data[i] = -1; stack[i] = i + 1;//从1到MAXN给栈加入数组下标 } return ; } int main()//演示链表的操作 { int ins,data; init(); while(1) { cout<<"1.insert a value in front \n"; cout<<"2.insert a value in rear \n"; cout<<"3.delete a value \n"; cout<<"4.quit \n"; cin>>ins; switch(ins) { case 1: cout<<"please insert a value :"; cin>>data; add_front(data); break; case 2: cout<<"please insert a value :"; cin>>data; add_rear(data); break; case 3: cout<<"please insert a value :"; cin>>data; del_by_data(data); break; default: return 0; } system("cls");//此命令只能在window平台下使用 output(); } return 0; }
以上便是我所学习的第一个数据结构--链表了,若有不足,请指正!!!