目录
一、线性表的思维导图
注意点:(1)线性表中的存储的数据元素必须具有相同数据类型。(2)线性表是一个有限序列。
二、线性表的基本概念
(1)线性表的定义:线性表是具有相同特性数据元素的一个有限序列。
(2)线性表的逻辑特性:只有一个表头元素,只有一个表尾元素,表头没有前驱,表尾没有后继,除表头和表尾元素之外,其他元素只有一个直接前驱,也只有一个直接后继。
(3)线性表的存储结构:线性表的存储结构有顺序存储和链式存储,前者成为顺序表,后者成为链表。
(a)顺序表:表中的元素按照其逻辑顺序,存储到一块连续的内存地址空间中。
(b)链表:每个节点不仅包含数据域,还包含指针域。如单链表中前驱节点的指针域存放后继节点的地址信息。
(4)链表的5种形式:
(a)单链表:每个节点由数据域和指针域组成,指针域用来指向其后继节点。
→带头节点的单链表:头指针head指向头节点,头节点一般不含任何信息,从头节点的后继节点开始存储信息。头指针始终不等于NULL,head→next等于NULL的时候,链表为空。
→不带头节点的单链表:头指针head直接指向开始节点。head等于NULL时,链表为空。
(b)双链表:每个节点由数据域和两个指针域组成,一个指向前驱节点,一个指向后继节点。
→带头节点的双链表:头指针始终不等于NULL,head→next等于NULL的时候,链表为空。
→不带头节点的单链表:头指针head直接指向开始节点。head等于NULL时,链表为空。
(c)循环单链表:在单链表的基础上把终端节点的指针域(空指针)指向链表中的第一个节点(可能是头节点)
→带头节点的循环单链表:当head等于head→next时,链表为空。
→不带头节点的循环单链表:头指针head直接指向开始节点。head等于NULL时,链表为空。
(d)循环双链表:在双链表的基础上把终端节点的next指针域(空指针)指向链表中的第一个节点(可能是头节点),将链表中第一个节点的prior指针指向终端节点。
→带头节点的循环双链表:head→next 等于 head→prior 等于 head 的时候链表为空
→不带头节点的循环双链表:头指针head直接指向开始节点。head等于NULL时,链表为空。
(e)静态链表:静态链表借助一维数组来实现,每一个数组元素中分别存储:序号、data域、"指针"域。
(5)顺序表和链表的比较:
(a)顺序表:具有随机访问特性、要求占用连续的内存地址空间、插入操作(不在终端节点)需要移动多个数据、插入和删除的平均时间复杂度为:O(n)。
(b)链表:不支持随机访问,只能顺序存取、节点的存储空间利用率较顺序表低一些(因为要有指针域)、插入操作无需移动元素,只需要修改指针、插入和删除的时间复杂度为:O(1)。
三、顺序表的结构体定义和基本操作
(1)顺序表的结构体定义:
#define maxSize 100 //这里宏定义顺序表的最大长度
typedef struct{
int data[maxSize]; //存放顺序表元素的数组
int length; //存放顺序表的长度
}Sqlist;
(2)初始化顺序表操作:只需要将length设置为 0。
void initList(Sqlist &L){//L本身要改变,所以用引用型
L.length = 0;
}
(3)顺序表的按值查找操作:在顺序表查找第一个等于e的元素,若找到则返回其在线性表的位置,若没找到则返回 0。
int findElem(Sqlist L, int e){
int i;
for(i=0;i<L.length;i++){
if(L.data[i] == e){
return ++i; //若找到,则返回其在线性表的位置
}
return 0; //没找到,返回0,作为查找失败的标记
}
}
(4)顺序表的按位置查找操作:在顺序表查找第p(0<p<L.length+1)个位置的元素,并将其返回给 e。
int findElem(Sqlist L, int p, int &e){
if(p<1||p>L.length){
return 0; //p越界,返回0
}
e = L.data[p+1]; //将第p个位置的元素返回给e
return 1; //查找成功返回 1
}
(5)顺序表的修改操作:查找顺序表中第一个等于e的元素,并对其值修改为x,修改成功返回 1,失败则返回 0。
int findAndUpdate(Sqlist &L, int e, int x){//L本身要改变,所以用引用型
int i;
for(i=0;i<L.length;i++){
if(L.data[i] == e){
L.data[i] = x;
return 1; //修改成功,则返回 1
}
}
return 0; //没找到,返回 0,作为查找失败的标记
}
(6)顺序表的插入操作:在顺序表的第p(0<p<L.length+1)个位置插入新元素e,如果p输入不正确则返回0,代表插入失败;如果p输入的正确,则将顺序表第p个元素以及以后的元素向后移一位,腾出一个空位置,插入新元素,顺序表长度增加1,插入成功,返回1。
int insertElem(&Sqlist &L, int p, int e){//L本身要改变,所以用引用型
int i;
if(p<0||p>L.length||L.length==maxSize){//位置错误或者表长达到最大值,插入不成功
return 0;
}
for(i=L.length-1;i>=p;i--){
L.data[i+1] = L.data[i]; //从后往前,逐个将元素向后移一位
}
L.data[p] = e; //将e放在插入位置p上
L.length ++; //表长加 1
return 1; //插入成功放回 1
}
(7)顺序表的删除操作:删除顺序表L的第p(0<p<L.length)个位置的元素,如果输入的p位置不正确则返回0,代表删除失败;如果p的位置输入正确,则将顺序表的第p个位置的元素赋值给e,然后将其后的元素依次向前移动一个位置,顺序表长度减少1,删除成功,返回1。
int deleteElem(Sqlist &L, int p, int &e){
int i;
if(p<1||p>L.Length){ //删除的位置不对,返回0
return 0;
}
e = L.data[p];
for(i=p;i<L.length-1;i++){ //从p位置开始,后边的元素逐个向前移动一个位置
L.data[i] = L.data[i+1];
}
L.length --; //删除成功表长 -1
return 1; //删除成功返回 1
}
四、单链表的结构体定义和基本操作
(1)单链表的节点结构体定义:
typedef struct LNode{
int data; //data存放数据域
struct LNode *next; //指向后继节点的指针
}LNode;
(2)头插法建立单链表:
void createListF(LNode *&C, int a[], int n){
LNode *s;
int i;
C = (LNode*)malloc(sizeof(LNode)); //申请C的头节点空间
C -> next = NULL; //将链表初始化为空
for(i=0;i<n;i++){
s = (LNode*)malloc(sizeof(LNode)); //s指向新申请的节点
s -> data = a[i]; //用s节点来接收a[]中的元素
s -> next = C -> next; //s所指的新节点的指针域的next指向C中的开始节点
C -> next = s; //头节点的指针域指向s节点,是s成为新的开始节点
}
}
(3)尾插法建立单链表:
void createListR(LNode *&C, int a[], int n){
LNode *s *r; //s指向新申请的节点,r指向C的终端节点
int i;
C = (LNode*)malloc(sizeof(LNode)); //申请C的头节点空间
C -> next = NULL; //将链表初始化为空
r = C; //r指向头节点,因为此时头节点就是终端节点
for(i=0;i<n;i++){
s = (LNode*)malloc(sizeof(LNode)); //s指向新申请的节点
s -> data = a[i]; //用s节点来接收a[]中的元素
r -> next = s; //用r来接纳新节点
r = r -> next; //将r指向终端节点,便于接纳下一个到来的节点
}
r -> next = NULL; //数组中的元素都插入到了链表中,此时将终端节点的指针域设置为NULL
}
(4)单链表的按值查找操作:查找单链表L中的第一个节点数据为e的元素,若找到则返回1,若没有则返回0;
int findList(LNode *C, int e){
LNode *p;
p = C; //将p指针指向单链表L的开始节点
while(p->next!=NULL){
if(p->next-data == e){
return 1; //查找成功返回 1
}
p = p->next;
}
return 0; //查找失败返回 0
}
(5)单链表的按位置查找操作:查找单链表L中的i个节点的元素,若找到则将其data数据返回给e,成功返回1,失败返回0;
int findList(LNode *C, int &e, int i){
int j=1; //标记当前位置
LNode *p;
p = C; //将p指针指向单链表L的开始节点
while(p->next!=NULL){
if(j!=i){
p = p->next;
j ++;
}else{
e = p.data;
return 1; //查找成功返回 1
}
}
return 0; //查找失败返回 0
}
(5)单链表修改操作:修改单链表中第一个节点数据为e的元素,将其修改为x,若修改成功则返回1,若失败则返回0;
int updateList(LNode *C, int e, int x){
LNode *p;
p = C; //将p指针指向单链表L的开始节点
while(p->next!=NULL){
if(p->next-data == e){
p->next-data == x;
return 1; //修改成功返回 1
}
p = p->next;
}
return 0; //修改失败返回 0
}
(5)单链表的插入操作:找到单链表的第i个位置,在其后面插入一个新节点s,成功返回1,失败返回0;
int insertList(LNode *C, int i, int e){
int j=1; //标记当前位置
LNode *p;
p = C; //将p指针指向单链表的开始节点
while(p!=NULL && j<i){ //寻找第i个节点
p = p->next;
j ++;
}
if(p==NULL){ //如果到末尾了还没找到,则返回 0
return 0;
}
s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return 1;
}
(6)单链表的删除操作:删除第 i 个位置的节点,将其data数据赋值给e,若成功则返回 1,若失败则返回 0;
int deleteList(LNode *C, int i, int *e){
int j=1; //标记当前位置
LNode *p, *q;
p = C; //将p指针指向单链表的开始节点
while(p->next!=NULL && j<i){ //寻找第i个节点
p = p->next;
j ++;
}
if(p->next==NULL){ //如果到末尾了还没找到,则返回 0
return 0;
}
q = p->next;
p->next = q->next;
e = q->data;
free(q);
return 1; //删除成功返回 1
}
三、双链表的结构体定义和基本操作
(1)双链表的节点结构体定义:
typedef struct DLNode{
int data; //data存放数据域
struct DLNode *next; //指向后继节点的指针
struct DLNode *prior; //指向前驱节点的指针
}LNode;
(2)双链表的插入操作:
s->next = p->next;
s-prior = p;
p->next = s;
s-next-prior = s; //加入p指向最后一个节点,则本行可以去掉
(3)双链表的删除操作:
q = p->next;
p-next = q->next;
q->next->prior = p;
free(q);
四、循环链表的操作
循环单链表和循环双链表是由对应的单链表和双链表改造而来,只需再终端节点和头节点之间建立联系即可。循环单链表终端节点的next节点指针指向表头节点;循环双链表终端节点的next指针指向表头节点,头节点的prior指针指向标为节点。需要注意的是,如果p指针沿着循环链表行走,则判断p走到表尾节点的条件是:p->next==head。循环链表的其余操作均与单链表类似。
五、静态数组简介
一般链表结点空间来自于整个内存,静态链表则来自于一个结构体数组。数组中的每一个结点含有两个分量:一个是数据元素分量data:另一个是指针分量,指示了当前结点的直接后继结点在数组中的位置(这和一般链表中next指针的地位是同等的)。
注意:静态链表中的指针不是我们通常所说的C语言中用来存储内存地址的指针型变量,而是一个存储数组下标的整型变量,通过它可以找到后继结点在数组中的位置,其功能类似于真实的指针,因此称其为指针。