线性表的链式存储结构
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以存在内存中未被占用的任意位置。
比起顺序存储结构,每个数据元素只需要存储一个位置就可以了。现在链式存储结构中,除了要存储数据元素信息外,还要存储它的后继元素的存储地址(指针)。
也就是说除了存储其本身的信息外,还需要存储一个指示其直接后继的存储位置的信息。
我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成数据元素称为存储映像,称为结点(Node)。
n个结点链接成一个链表,即为线性表(a1,a2,a3,…,an)的链式存储结构。
因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
我们把链表中的第一个结点的存储位置叫做头指针,最后一个结点指针为空(NULL)。
头指针与头结点的异同
单链表存储结构
单链表图例:
空链表图例:
我们在C语言中可以用结构指针来描述单链表。
typedef struct Node{
ElemType data; //数据域
struct Node *Next; //指针域
} Node,*LinkList;
假设p是指向线性表第i个元素的指针,则该节点a[i]的数据域我们可以用p->data的值来表示,是一个数据元素,结点a[i]的指针域可以用p->next来表示,p->next的值是一个指针。
那么p->next指向谁呢?当然是指向第i+1个元素!
也就是指向a[i+1]的指针。
问题:
-如果p->data = a[i],那么p->next->data = ?
-答案:p->next->data = a[i+1]
单链表的读取
算法思路:
-声明一个结点P指向链表的第一个结点,初始化j,从1开始;
-当j<i时,就遍历链表,让p的指针向后移动,不断指向下一节点,j+1;
-若到链表末尾p为空,则说明第i个元素不存在;
-否则查找成功,返回结点p的数据。
/*初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
操作结果:用e返回L中第i个数据元素的值*/
Status GetElem(LinkList &L,int i,ElemType &e){
int j;
LNode *p;
p = L->next;//指向首元结点
j = 1;
while(p && j<i){
p = p->next;
++j;
}
if(!p || j>i){
return ERROR;
}
e = p->data;
return OK;
}
单链表的插入
我们思考后发觉根本用不着惊动其他节点,只需要要让s->next和p->next的指针做一点改变。
s->next = p->next;
p->next = s;
注意,不要弄反!
单链表第i个数据插入结点的算法思路:
-声明一节点p指向链表头结点,初始化j,从1开始;
-当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1;
-若到链表尾部p为空,则说明第i个元素不存在;
-否则查找成功,在系统中生成一个空结点s;
-将数据元素e赋值给s->data;
-单链表的插入(刚才的两个标准语句)
-返回成功
有头结点
Status ListInsert(LinkList &L,int i,ElemType e){
int j;
LNode *p;
p = L;
j = 0;
while(p && j<i-1){
p = p->next;
++j;
}
if(!p || j>i-1){
return ERROR;
}
LNode *s;
s = new Node;
if(!s) return ERROR;
s->data = e;
s->next = p->next;
p->next = s;
return OK;
//return InsertNextNode(p,e);
}
无头结点
如果不带头结点,则插入,删除第1个元素时,需要修改头指针L
//无头结点的插入
Status ListInsert(LinkList &L,int i,ElemType e){
if(i == 1){ //插入第一个结点的操作与其他节点不同
LNode *s;
s = new LNode;
s->data = e;
s->next = L;
L = s; //头结点指向新结点
return true;
}
int j
LNode *p,*s;
p = new LNode;
p = L; //p指向第1个结点,(注意:不是头结点)
j = 1; //当前p指向的是第几个结点
while(p && j<i-1){
p = p->next;
j++;
}
if(!p || j>i-1){
return ERROR;
}
s = new LNode;
if(!s) return ERROR;
s->data = e;
s->next = p->next;
p->next = s;
return OK;
//return InsertNextNode(p,e);
}
指定结点的后插操作
Status InsertNextNode(LNode *p,ElemType e){
if(!p) return ERROR;
LNode *s;
s = new LNode;
if(!s) return ERROR;
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
指定结点的前插操作(先建立结点,再交换数据)
第一种写法
Status InsertPriorNode(LNode *p,ElemType e){
if(!p) return ERROR;
LNode *s;
if(!s) return ERROR;
s->next = p->next;
p->next = s;
s->data = p->data;
p->data = e;
return OK;
}
第二种写法
Status InsertPriorNode(LNode *p,LNode *s){
if(!p && !s) return ERROR;
s->next = p->next;
p->next = s; //s连到p之后
ElemType temp; //交换数据域部分
temp = p->data;
p->data = s->data;
s->data = temp;
return OK;
}
单链表的删除(带头结点)
假设元素a[2]的结点为q,要实现结点q删除单链表的操作,其实就是将它的前继节点的指针绕过,指向后继结点即可。
可以这样
p->next - p->next->next;
也可以是
q = p->next;
p->next = q->next;
算法思路:
-声明结点p指向链表第一个结点,初始化j=1;
-当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
-若到链表末尾p为空,则说明第i个元素不存在;
-否则查找成功,将欲删除结点p->next赋值给q;
-单链表的删除标准语句p->next = q->next;
-将q结点中的数据赋值给e,作为返回;
-释放q结点。
//删除表L中第i个位置的元素,并用e返回删除元素的值
//头结点看作第0个结点
Status ListDelete(LinkList &L,int i,ElemType &e){
LNode *p;
p=L;
int j=0;
while((p->next) && j<i-1){
p = p->next;
++j;
}
if(!(p->next) || j>i-1){
return ERROR;
}
q = p->next;
p->next = q->next;
e = q->data;
delete q;
return OK;
}
指定结点的删除(不传入头结点)
首先声明一个指针q指向p的后继结点
然后把p的后继结点的数据赋值到p的数据域中
然后让p的后继指针指向q的后继结点(可能是一个结点,也可能是NULL)
最后释放掉q
Status DeleteNode(LNode *p){
if(!p) return ERROR;
LNode *q;
q = p->next;
p->data = p->next->data; // p->data = q->data;
p->next = q->next;
delete q;
return OK;
}
}
单链表的遍历
void DisplayList(LinkList &head){
head = head->next; //指向首元结点
while(head != NULL){
cout<<head->data<<" ";
head = head->next;
}
cout<<endl;
}