在日常中出现需要存储一串数据的情况时,我们的第一反应就是想到运用数组。
单链表的背景及与数组的优劣势对比。
数组有其独特的优势,数组声明后便在内存中随机分配一片连续的,大小为Arry_length*sizeof (elem_type)的内存空间。单位数据占用空间小,在查找过程中,查找规则简单方便,效率高。其劣势也比较明显,若内存内没有满足条件的连续的内存空间,则无法分配内存。而计算机中的数据都是零散排放的,在出现大批量的数据的时候很难找到与其相对应的连续的内存空间,并且在做一些插入、删除等操作时需要耗费的时间复杂度略高。
我们引用链表的形式来解决这个问题。我们将数组中的元素包装成结构体,并且在结构体内添加一个指针元素,这样一个链表的节点就定义完成。
在创建链表的过程中预留一个头节点作为标志,该标志可以用于快速查看链表的其他信息,以此来减少工作量。链表中的各个节点均靠结构体中的next指针来引路,依次指向下一个节点。做一个形象的比喻,一条链表就可以比作一列绿皮火车,火车中每个车厢都是一个节点,火车头就是链表的头指针,可以存放火车中的车箱数,人数等信息。每一个车厢间的联系,均需要靠车厢中的next指针链接在一起。
链表的优势相当明显,即链表不需要像数组一样大片连续的空间,而只需要一个节点大小的空间,并且对于插入、删除等操作有极强的便利性。
总结数组与链表的优劣势:
数组:
优势:简单易学,占用空间小,数组内访问查找效率高,适合随机查找。
劣势:
空间上:空间大小固定,申请数组空间要求苛刻,不适合动态存储。
时间上:对插入删除等操作时间复杂度略高,不适合动态添加。
链表:
优势:插入删除操作效率高、内存利用率高、可拓展性强。
缺点:占用空间较大,只能通过指针顺序查找,查找效率低。
单链表的构成:
我们将数组内的数据同指针一起封装在一个结构体中,每个结构体就是一个链表的节点。单链表是以指针作为连接,各个节点都由next指针指向下一个节点的地址,将各个节点相互连接起来形成一个单向链表。
程序示例:
结构体:
structLnode {
element_type elem;
struct node *next;
}Lnode;
示例:
#include<bits/stdc++.h>
#defineelement_type int
usingnamespace std;
typedefstruct Lnode {
element_type val;
struct Lnode *next;
}Lnode,*LinkList;
intcreat_head(LinkList &L){
L = (LinkList) malloc (sizeof (Lnode));
if (!L) return 0;
L->next = NULL;
cout<<"creatsuccess!"<<endl;
}
voidcreat_node (LinkList &L){
LinkList pt,q;
pt = L;
cout<<"inputn"<<endl;
int n;cin>>n;
while(n--){
q = (LinkList) malloc (sizeof (Lnode));
if (!q) {cout<<"mallocfail"<<endl;continue;}
cout<<"cinval"<<endl;
cin>>q->val;
pt->next = q;
pt = pt->next;
pt->next = NULL;
}
}
voidshow_List(LinkList &L){
LinkList pt = L->next;
while (pt != NULL){
cout<<pt->val<<" "<<endl;
pt = pt->next;
}
}
intmain(){
LinkList L;
while (1){
int ch=0;cin>>ch;
if (ch==1) creat_head(L);
else if (ch==2) creat_node(L);
else show_List(L);
}
}
拓展:对于存储一串连续的数据时,不止可以使用单链表。单链表之外还有循环单链表、双向链表、循环双向链表等等。对于没有指针这一工具之前的程序员们,他们是无法直接使用链表这一存储结构的,但链表的思想已经存在。他们运用定长数组在模拟链表的工作。这就是数组模拟链表。
循环单链表:
循环链表是在单链表的基础上做出一些修改。链表第一个元素作为表头,不存储任何信息,表头下一个元素即L->next作为head,将最尾部的元素的指针指向head,从而形成一个循环。这种做法的好处是可以实现任意位置作为遍历的起始位置。
示例:
#include<bits/stdc++.h>
usingnamespace std;
typedefstruct node {
int val;
struct node *next;
}node,*LinkList;
voidCreateList(LinkList &L){
L = (LinkList )malloc (sizeof (node));
if (!L) cout<<"malloc fail!"<<endl;
LinkList pnew,ptail,phead;
cout<<"cin n:";
int n;cin>>n;
for (int i=1;i<=n;i++){
int v;cin>>v;
pnew = (LinkList )malloc (sizeof(node));
pnew->val = v;
if(i==1){
L->next = pnew;
phead = pnew;
ptail = phead;
}
else {
pnew->next = phead;
ptail->next = pnew;
ptail = pnew;
}
}
}
voidShow(LinkList &L){
LinkList phead,pt;
//phead = L->next;
//pt = phead->next;
pt = L->next;
int cnt=10;
while (cnt--){
cout<<pt->val<<endl;
pt = pt->next;
}
}
intmain()
{
LinkList L;
CreateList(L);
Show(L);
}
双向链表:
双向链表是在单链表的基础上添加一个prior指针,该指针总是指向当前元素的前一个元素的地址。牺牲这一部分空间所获得的好处是,双向链表可以实现前后的查询。
示例:
#include<bits/stdc++.h>
usingnamespace std;
typedefstruct node{
int val;
struct node *next ;
struct node *prior;
}node,*LinkList;
voidCreateList (LinkList &L){
L = (LinkList)malloc (sizeof (node));
if (!L) cout<<"malloc fail!"<<endl;
cout<<"cin n:";
int n;cin>>n;
LinkList rear,pnew;
rear = L;
int v;
for (int i=1;i<=n;i++){
cin>>v;
pnew = (LinkList)malloc (sizeof(node));
if (!pnew) continue;
pnew->val = v;
rear->next = pnew;
pnew->prior = rear;
rear = pnew;
rear->next = NULL;
}
}
voidShow1 (LinkList &L){
LinkList pt;
pt = L->next;
while (pt){
cout<<pt->val<<endl;
pt = pt->next;
}
cout<<endl;
}
voidShow2(LinkList &L){
LinkList pt;
pt = L->next;
while (pt->next){
pt = pt->next;
}
while (pt!=L){
cout<<pt->val<<endl;
pt = pt->prior;
}
}
intmain()
{
LinkList L;
CreateList(L);
Show1(L);
Show2(L);
}