静态链表、循环链表、双向链表
补充:创建单链表(首插法和尾插法)
动态建立单链表:从一个空表开始,通过插入操作完成
//单链表的结构体定义
typedef struct Node{
int data;//结点中存放的数据
struct Node *next;
}LNode,*LinkList;
首插法
首插法建立单链表的基本思想:每次在头结点后插入。
- 建立一个带表头结点的空链表
//创建一个空链表
LinkList L;
L=(LinkList)malloc(sizeof(Node));
L->next=NULL;
- 依次逆序读入线性表中的数据元素,将其插入到表中,做表头结点的直接后继
p=(LinkList)malloc(sizeof(Node));//新建结点
scanf("%d",&p->data); //输入数据
p->next=L->next;//使用 首插法 插入结点
L->next=p;//插入到了 头节点之后 成为第一个结点
完整算法示例:
//首插法
void Create_LinkList(LinkList &L, int n){
//形参 n 表示 需要新建的结点个数
LinkList p;//
L=(LinkList)malloc(sizeof(Node));//申请表头结点空间
L->next=NULL;//不要忘记 这一步!!!//建立一个带表头结点的空表
for (int i = n; i > 0; --i){
//依次逆序 因此倒着(循环)输入数据 使结果 变为正序
p=(LinkList)malloc(sizeof(Node));//申请存放数据元素的空间
scanf("%d",&p->data);//读入的值放入所申请空间的data域
p->next=L->next;//插入链表-原先表头结点的直接后继作为新插结点的直接后继
L->next=p;//新插结点作为表头结点的直接后继
}
} //T(n)=O(n)
尾插法
尾插法建立单链表的基本思想:每次在链表尾插入
- 建立一个带表头结点的空链表
//创建一个空链表
LinkList L;
L=(LinkList)malloc(sizeof(Node));
L->next=NULL;
- 依次顺序读入线性表中的数据元素,将其插入到表中,做表尾结点(s)的直接后继 初始——s=L;//s为尾结点 每读入一个数据元素——①新生成结点,数据存入②s->next=p;③s=p //p为指向新结点的指针
//比首插法需要 多创建一个 链表指针s 用来做链表的尾结点
void Create_LinkList(LinkList &L,int n){
//形参 n 为要插入元素的个数
LinkList p,s;//s 作尾结点
L=(LinkList)malloc(sizeof(Node)); //申请表头结点空间
L->next=NULL;//不要忘记 这一步!!!//建立一个带表头结点的空表
s=L;//设置单链表的尾节点s为当前链表的头结点,因为空表只有表头结点
for(int i =0;i<n;i++){//依次读入n个数据元素插入表中
p=(LinkList)malloc(sizeof(Node));//申请存放数据元素的间
scanf("%d",&p->data);
p->next=NULL;//新插入的数据作为链表尾结点,加尾结点标记
s->next = p;//新插入链表为原先尾结点s的直接后继
s=p;//新插入的结点尾插入后的尾结点
}
}
静态链表
静态链表是分配一整片连续的内存空间,各个结点集中安置,而不是像单链表那样,每个结点在内存中分散存储。
- 优点:其存储方式的优点类似于顺序表,但是在这一整片连续的内存空间中,元素不是按照顺序存储的。而是可以打乱顺序存储,这时候就需要将结点分为两个部分,一个部分存储数据元素,另一个部分存储下一个结点所在的下标(称其为游标)。(增、删操作不需要大量移动元素)
- 缺点:和普通链表一样,不能随机存取,只能从头结点开始以此往后查找。且容量固定不可变。
//以下使用c语言代码 来介绍静态链表
#define MAX_SIZE 100 // 静态链表的最大长度
// 单链表的结构体定义
typedef struct Node {
int data; // 结点中存放的数据
struct Node *next; // 指向下一个结点的指针
} LNode, *LinkList;
// 静态链表的结构体定义
typedef struct {
LNode nodes[MAX_SIZE]; // 结点数组
int length; // 链表长度
} StaticList;
// 创建静态链表
void Create_StaticList(StaticList *list, int data[], int length) {
if (length > MAX_SIZE) {
printf("Error: Exceeded maximum size of the static list.\n");
//由于 静态链表容量固定不可变。要判断是否越界
return;
}
for (int i = 0; i < length; ++i) {
list->nodes[i].data = data[i];
if (i == length - 1)
list->nodes[i].next = NULL;
//输入完毕后 最后一个结点的后继指向为NULL
else
list->nodes[i-1].next = &list->nodes[i];
}
list->length = length;
}
// 输出静态链表
void printStaticList(StaticList *list) {
LNode *p = list->nodes;
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
//示例:
int main() {
int data[] = {1, 2, 3, 4, 5}; // 要存入链表的数据
int length = sizeof(data) / sizeof(data[0]); // 数据长度
StaticList list;
Create_StaticList(&list, data, length); // 创建静态链表
printf("Static List: ");
printStaticList(list); // 输出静态链表
return 0;
}
循环单链表
循环单链表:将单链表的尾结点的指针强行指向单链表的头结点
特点:
- 从表中任一结点出发均能找到表中所有结点
- p结点为尾结点的条件: p->next ==h;
- 循环单链表为空表的判断条件:h->next = h;
例题1:某线性表中最常用的操作是在最后一个元素之后插入一个元素和删除第一个元素,则采用( )存储方式最节省运算时间。
A .单链表
B .仅有头指针的单循环链表
C .双链表
D .仅有尾指针的单循环链表
选D。对于A,B,C要想在尾端插入结点,需要遍历整个链表。
//对于D, //尾结点指针为p,假设插入q,
q->next=p->next;
p->next=q;
//删除第一个元素
temp = p->next;
p->next = temp->next;
free(temp);
例题2:设一个链表最常用的操作是在末尾插入结点和删除尾结点,则选用( )最节省时间。
A . 单链表
B .单循环链表
C .带尾指针的单循环链表
D .带头结点的双循环链表
选D. 对于A、B需要遍历整个链表 对于C
带尾指针的单向循环链表插入元素可以,但是删除尾结点不行,因为是单向的,所以在删除尾结点之前无法得到尾结点之前的那个结点。
双向链表
双向链表:单链表的每个结点包含2个指针,分别指向结点的直接前驱和直接后继
特点:
- 从表中任一结点p出发均能找到表中所有结点,设p结点存放的是线性表的数据元素a[i],沿着next指针能找到a[i+1],a[i+2],…,a[n].沿着prior指针能找到a[i-1],a[i-2],…,a[1].
- 插入和删除操作,结点的2个指针均要连接上! 例题:双向链表中有两个指针域,llink 和 rlink,分别指回前驱及后继,设 p 指向链表中的一个结点,q指向一待插入结点,现要求在 p 前插入 q,则正确的插入为()
//1.
p->llink->rlink =q;
q->rlink =p;
q->llink =p->llink;
p->llink:=q;
//2.
q->Rlink=p;
q->Llink=p->Llink;
p->Llink->Rlink=q;
p->Llink=q;
双向循环链表
例题:在双向循环链表中,在p指针所指的节点后插入一个指针q所指向的新节点,修改指针的操作是____。
q->prior=p;
q->next=p->next;
p->next->prior=q;
p->next=q;
小结
■ 逻辑相邻不一定物理相邻
■ 只能顺序存取
■ 插入和删除操作不需要移动数据
■ 按值查找O(n),和顺序存储结构的按值查找速度相同
■ 按数据元素的位置查找O(n),比顺序存储结构的按位置查找速度慢