目录
前言
本章介绍怎么使用数组模拟链表,首先得理解其定义
链表是指由一系列储存在非连续储存空间 结点组成的储存结构。每个结点由两部分组成:一是储存元素的数据域,一是储存下一个节点地址的指针域。用数组模拟链表得十分清晰明了地理解这一定义。
为什么要用数组模拟实现呢?数组实现的双链表本质上是静态链表,但是具有实现简单,速度极快等特点,比数据结构书上声明新的数据类型的方法代码简洁,好记忆。
数组是一种线性结构,存储空间是内存连续的,每当创建一个数组时,就必须先申请好一段指定大小的空间。由于内存连续这一特性,数组的访问特别快,知道下标索引后,就可以通过地址偏移计算得到指定位置的地址,进而能够直接访问到那个地址空间的数据。数组要保证内存连续这一特性,数组在进行任意位置的插入和删除元素时,就会涉及到部分元素的移动,即插入第i个位置时,需要把i+1位置及其后面的所有元素都往后移动一位,然后执行插入;或者删除第i个位置时,在删除后,需要把第i+1位置及其后面的所有元素都往前移动一位,这样才能保证数据的内存连续。
一、数组模拟单链表
数组可以模拟实现单链表和双链表,我们先来介绍单链表。
代码如下
int head, e[N], ne[N], idx;
// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
void init()//初始化
{
head = -1;idx = 0;
}
void insert(int a)// 在链表头插入一个数a
{
e[idx] = a,ne[idx] = head, head = idx ++ ;
}
void remove(int k)// 将k位置删掉
{
ne[k]=ne[ne[k]];
}
for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';//输出链表元素
二、数组模拟双链表
// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;
void init()//初始化0是左端点,1是右端点
{
r[0] = 1, l[1] = 0;
idx = 2;
}
void insert(int a, int x)// 在节点a的右边插入一个数x
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}
void remove(int a)// 删除节点a
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
insert(0,x);//插到头节点后;头节点下标是0
insert(l[1],x);//插入到队尾,尾节点下标为1,所以尾节点的左节点下标为l[1]
insert(l[k],x);//插入到k节点左边,k节点的左下标为了l[k]
for (int i = r[0]; i != 1; i = r[i])cout << e[i] << ' ';//输出链表元素
图解代码
三、对比
如果不用数组实现链表,代码的长度会很长,但是也有它的优点,好理解。
单链表代码如下
#define ERROR 0
#define OK 1
typedef int Status;
typedef int ElemType;
struct Node{
ElemType data;
struct Node * next;
};
typedef struct Node *LinkList;
void InitList(LinkList *L){
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
}
bool ListEmpty(LinkList L){
if(L->next)
return false;
else
return true;
}
void ClearList(LinkList *L){
LinkList p,q;
p = (*L)->next;
while(p){
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
}
int ListLength(LinkList L){
int i = 0;
LinkList p = L->next;
while(p){
i++;
p = p->next;
}
return i;
}
Status GetElem(LinkList L,int i,ElemType *e){
int cnt = 1;
LinkList p = L->next;
while(p && cnt < i){
p = p->next;
cnt++;
}
if(!p)
return ERROR;
*e = p->data;
return OK;
}
int LocateElem(LinkList L,ElemType e){
int cnt = 0;
LinkList p = L->next;
while(p){
cnt++;
if(p->data == e)
return cnt;
p = p->next;
}
return 0;
}
Status ListInsert(LinkList *L,int i,ElemType e){
LinkList p,s;
p = (*L);
int cnt = 1;
while(p && cnt < i){
cnt++;
p = p->next;
}
if(!p)
return ERROR;
s = (LinkList)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
Status ListDelete(LinkList *L,int i,ElemType *e){
int cnt = 1;
LinkList q,p;
p = (*L);
//此时 p 为头节点,p->next为第一个节点
while( p->next && cnt < i){
p = p->next;
cnt++;
}
//第一个节点不为空,并且 cnt 小于要删除的节点位置,开始循环
if(!(p->next))
return ERROR;
q = p->next;
p->next = q->next;
*e = q->data;
free(q);
return OK;
}
Status ListTraverse(LinkList L){
LinkList p = L->next;
while(p){
printf("%d ",p->data);
p = p->next;
}
printf("\n");
return OK;
}
void CreateListHead(LinkList *L,int n){
LinkList p;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
for(int i = 0 ; i < n ; i++){
p = (LinkList)malloc(sizeof(Node));
p->data = rand()%100 + 1;
p->next = (*L)->next;
(*L)->next = p;
}
}
void CreateListTail(LinkList *L,int n){
LinkList p,r;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L;
for(int i = 0 ; i < n ; i++){
p = (LinkList)malloc(sizeof(Node));
p->data = rand()%100 + 1;
r->next = p;
r = p;
}
r->next = NULL;
}
对比发现代码长了非常多。这里只对比了单链表,如果有兴趣可以看看自行对比一下双链表
相较于数组模拟,数组更好写,更好记忆,如果参加一些比赛的话我更加推荐使用数组模拟的链表,另外C++ STL里的list,也是可以的。代码如下
#include <list>
list<int> a;
总结
数组模拟链表经常作为算法的模板使用,要学会这个得了解基础链表的基本概念和实现方式,并且多写多用。如果不是很好理解的话可以尝试跟着代码画一遍图,就可以很好的理解了。