1.3 线性表的链式存储结构

粗略的定义:用一组地址任意的存储单元存放线性表中的数据元素。


一、单链表:以元素 + 指针(指示后继元素位置) = 结点(表示数据元素)


以线性表中的第一个数据元素a1的存储地址作为线性表的地址,称作线性表的头指针。
为了操作方便,在第一个结点之前虚加一个“头结点”,以指向结点的指针为链表的头指针。

二、结点和单链表的C语言描述
ADT(Abstract Data Type):抽象数据类型
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域
}LNode, *LinkList;
注:理解:typedef (struct LNode*) (LinkList)


LinkList L; //L为单链表的头指针。


三、单链表操作的实现
GetElem(L, i, &e)      //取第i个数据元素
ListInsert(&L, i, e)  //插入数据元素
ListDelete(&L, i, e)  //删除数据元素
ClearList(&L)         //重新置为一个空表
CreateList(&L, n)     //生成含n个数据元素的链表


线性表的操作:
GetElem(L, i, &e)在单链表中的实现:
单链表是一种顺序存取的结构,为找第i个数据元素,必须先找到第i - 1个数据元素。
因此,查找第i个数据元素的基本操作为:移动指针,比较j(计数器)和i。
令指针P始终指向线性表中第j个数据元素。


Status GetElem_L(LinkList L, int i, ElemType &e){
//L是带头结点链表的头指针,e返回第i个元素
p = L-> next;            //p指向第1个结点
j = 1;                   //j为计数器
while(p && j < i){       //顺指针向后查找,直到p指向第i个元素
p = p -> next;
++j;
}
if(!p || j > i)
return ERROR;    //元素不存在 or i值不合理
e = p -> data;           //取得第i个元素
return OK;
}//GetElem_L
时间复杂度:O(n)--> O(ListLength(L))


线性表的操作:
ListInsert(&L, i, e)在单链表中的实现:
在链表中插入结点只需要修改指针。
但同时,若要在第i个结点之前插入元素,修改的是第i - 1个结点之前的指针。
因此,在单链表中第i个结点之前进行插入的基本操作为:
找到线性表中第i - 1个结点,然后修改其指向后继的指针。


Status ListInsert_L(LinkList &L, int i, ElemType e){
//L为带头结点的单链表的头指针
//在链表中第i个结点之前插入新的元素e
p = L;
j = 0;
while(p && j < i - 1){    //寻找第i - 1个结点
p = p -> next;
++j;
}
if(!p || j > i - 1)       //......
return ERROR;
s = new LNode;  //生成新节点
if(s == NULL)
return ERROR;
s -> data = e;            //插入         
s -> next = p -> next;
p -> next = s;
return OK;
}
算法的时间复杂度:O(ListLength(L)) --> 这里的复杂度与顺序表的插入复杂度O(n)是有区别的!!!
主要体现在查找方面 --> 具体操作方面 O(1)


前插结点:设P指向链表中某结点,s指向待插入的为x的新结点,将*s插入到*p的前面。
(第一种方法)与后插不同的是:首先要找到*p的前驱*q(...),然后再完成在*q之后
插入*s, 设单链表头指针为L。
操作如下:
1. q = L;
  while( q -> next != p)  //找到*p的直接前驱
  q = q -> next;
  s -> next = q -> next;
  q -> next = s;
  时间复杂度:O(n)
(第二种方法)前插操作需要找*p的前驱。(上一个方法)可以将*s插入到*p的后面(后插)
2. s -> next = p -> next;
  p -> next = s;
  然后再交换数据域即可;
  p -> data = s -> data;
  时间复杂度:O(1)


线性表的操作:
ListDelete(&L, i, &e)在单链表中的实现:
基本操作:
找到线性表中第i - 1个结点,修改其指向后继的指针。
q = p -> next;
p -> next = q -> next;
e = q -> data;
delete(q);
也可以:
p -> next = p -> next -> next;
dalete(...)// 可见这种方法的弊端了吧


Status ListDelete_L(LinkList &L, int i, ElemType &e){
//删除以L为头指针(带头结点)单链表中第i个结点
p = L;
j = 0;
//寻找第i个结点,并令p指向被删结点的前驱结点
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;
}//ListDelete_L
时间复杂度:O(n)


线性表的操作:
ClearList(&L)在单链表中的实现:


void ClearList(&L){
//将单链表重新置为一个空表
while(L -> next){
p = L -> next;
L -> next = p -> next;
delete(p);
}
}
时间复杂度:O(n)


头插法:逆位序输入n个数据元素的值,建立带头结点的单链表。
例: 输入 1, 2, 3, 4, 5
    得到 5, 4, 3, 2, 1
    操作步骤:
    一、建立一个"空表";
    二、输入数据元素an, 建立结点并插入;
    三、输入数据元素a(n -1), 建立结点并插入;
    四、依次类推,直到a1;


    void CreateList_L(LinkList &L, int n){
       //头插法建立带头结点的单链表
//先建立一个带头结点的单链表
L = new LNode;
L -> next = NULL;
for(i = n; i > 0; --i){
p = new LNode;
scanf(&p -> data);
p -> next = L -> next;
L -> next = p;

    }
    时间复杂度:O(n)
尾插法:.....(向对于头插法)。
(老头子们的)定义:正序输入n个数据元素的值。
建立不带头结点的单链表尾插法:


LinkList Create_LinkList2(){
LinkList L = NULL;
LNode *s, *r = NULL;
int x;
scanf("%d", %x);
while(x != flag){              //flag为结束标记
s = (LNode*)malloc(sizeof(LNode));
s -> data = x;
if(L == NULL)          //第一个结点的处理
L = s;
else                   //其他结点的处理
r -> next = s;
r = s;                 //r指向新的尾节点
scanf("%d", &x);
}
if(r != NULL)                  //非空表最后结点的指针域为空
r -> next = NULL;
return L;


}
建立带头结点的单链表尾插法:
LinkList Create_LinkList2(){
LinkList L = (LNode *)malloc(sizeof(LNode));
L -> next = NULL;
LNode *r = L, *s;
int x;
scanf("%d", &x);
while(x != flag){
s = (LNode *)malloc(sizeof(LNode));
s -> data = x;
r -> next = s;
r = s;          //r指向新的尾结点
scanf("%d", &x);
}
r -> next = NULL;
return L;
}


头结点的两个优点:
a、由于开始结点的位置被存放在头结点的指针域中,所以在链表的第一个位置上的操作
就和在表中的其他位置上的操作一致,无需进行特殊处理;
b、无论链表是否为空,其头指针是指向头结点的非空指针(空表中头结点的指针域为空)
因此空表和非空表的处理就统一了呗。

猜你喜欢

转载自blog.csdn.net/l158943041/article/details/80546509
今日推荐