链式存储【链表】
1.链表的定义
逻辑上连续,物理上不连续
彼此通过指针相连
每个结点只有一个前驱结点,每个结点只有一个后继结点
首结点只有一个前驱,尾结点只有一个后继
typedef struct LNode {
int data; //数据域
pLNode pNext; //指针域,pNext是struct Node*类型
}LNode,*pLNode;
(1)链表的特点
- 在内存中可以存在任何地方,不要求连续。
- 每一个数据都保存了下一个数据的内存地址,通过这个地址找到下一个数据。 第一个人知道第二个人的座位号,第二个人知道第三个人的座位号……
- 增加数据和删除数据很容易。 再来个人可以随便坐,比如来了个人要做到第三个位置,那他只需要把自己的位置告诉第二个人,然后问第二个人拿到原来第三个人的位置就行了。其他人都不用动。
- 查找数据时效率低,因为不具有随机访问性,所以访问某个位置的数据都要从第一个数据开始访问,然后根据第一个数据保存的下一个数据的地址找到第二个数据,以此类推。 要找到第三个人,必须从第一个人开始问起。
- 不指定大小,扩展方便。链表大小不用定义,数据随意增删。
(2)专业术语:
首结点:
第一个有效结点
尾结点:
最后一个有效结点
头结点:
头结点的数据类型和首结点类型一样
第一个有效结点之前的那个结点
头结点并不存放有效数据
加头结点的目的主要是为了方便对链表的操作
头指针:
指向头结点的指针变量
尾指针:
指向尾结点的指针变量
(3)如果希望通过一个函数来对链表进行处理,我们至少需要接收链表的哪些参数
如果希望通过一个函数来对链表进行处理,我们至少需要接收链表的哪些参数
只需要一个参数:头指针
以为我们通过头指针可以推算出链表的其他所有参数
2.分类
(1)单链表
(2)双链表
每一个结点有两个指针域
(3)循环链表
能透过任何一个结点找到其他所有结点
(4)非循环链表
3.算法
(1)主函数和函数声明
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
typedef struct LNode {
int data; //数据域
struct LNode* pNext; //指针域,pNext是struct Node*类型
}LNode, * pLNode;
pLNode create_list();//创建链表
void show_list(pLNode pHead);//遍历链表
bool is_empty(pLNode pHead);//判断是否为空
int length_list(pLNode pHead);//求链表长度
bool insert_list(pLNode pHead, int pos, int val);//插入
bool delete_list(pLNode pHead, int pos, int* val);//删除
void sort_list(pLNode pHead);//排序
int main() {
int val, pos;
pLNode pHead = NULL; //等价于struct Node*pHead= NUll
//初始化
pHead = create_list();
show_list(pHead);
//链表长度
printf("链表长度为%d\n", length_list(pHead));//链表长度
//排序
sort_list(pHead);
show_list(pHead);
//插入
printf("请输入想要插入到第几个结点之后:");
scanf_s("%d", &pos);
printf("请输入想要插入的值:");
scanf_s("%d", &val);
if (insert_list(pHead, pos, val))
{
printf("插入成功\n");
}
show_list(pHead);
//删除
printf("请输入想要删除的结点位置:");
scanf_s("%d", &pos);
if (delete_list(pHead, pos, &val))
{
printf("删除成功\n");
printf("删除的值为:%d\n", val);
}
show_list(pHead);
return 0;
}
(2)分配地址初始化
pLNode create_list() //创建链表
{
int len; //用来存放有效结点的个数
int val; //用来临时存放用户输入的结点的值
//分配了一个不存放有效数据的头结点
pLNode pHead = (pLNode)malloc(sizeof(LNode));
if (pHead == NULL)
{
printf("分配失败\n");
exit(-1);
}
pLNode pTail = pHead; //pTail永远指向尾结点
pTail->pNext = NULL;
printf("请输入您要生成的链表结点的个数:len=");
scanf_s("%d", &len);
for (int i = 0; i < len; i++)
{
printf("请输入第%d个数", i + 1);
scanf_s("%d", &val);
//分配一个新的结点
pLNode pNew = (pLNode)malloc(sizeof(LNode));
if (pNew == NULL)
{
printf("分配失败\n");
exit(-1);
}
pNew->data = val;
pTail->pNext = pNew;
pNew->pNext = NULL; //清空尾结点的指针域
pTail = pNew; //更新尾结点
}
return pHead;
}
(3)遍历链表
void show_list(pLNode pHead) //遍历链表
{
if (is_empty(pHead) == false)
{
pLNode p = pHead->pNext;
printf("链表数据为:", p->data);
while (p != NULL)
{
printf("%d ", p->data);
p = p->pNext;
}
printf("\n");
}
else
{
printf("链表为空\n");
}
}
(4)求链表长度
int length_list(pLNode pHead)//求链表长度
{
int i = 0;
if (is_empty(pHead) == false)
{
pLNode p = pHead->pNext;
while (p != NULL)
{
p = p->pNext;
i++;
}
}
return i;
}
(5)插入结点
bool insert_list(pLNode pHead, int pos, int val)//插入,pos从1开始
{
int i = 1;
pLNode p = pHead;
while (p != NULL && i <= pos) //通过循环,寻找到pos的位置
{
p = p->pNext;
i++;
}
if (i > pos || p == NULL)
{
printf("位置不合法,插入失败\n");
return false;
}
pLNode pNew = (pLNode)malloc(sizeof(LNode));
pLNode z = p->pNext; //临时结点
p->pNext = pNew;
pNew->data = val;
pNew->pNext = z;
return true;
}
(6)删除结点
bool delete_list(pLNode pHead, int pos, int* val) //删除
{
int i = 1;
pLNode p = pHead;
while (p != NULL && i < pos) //通过循环,寻找到pos前面的位置
{
p = p->pNext;
i++;
}
if (i > pos || p == NULL)
{
printf("位置不合法,插入失败\n");
return false;
}
pLNode z = p->pNext; //临时结点
*val = z->data;
p->pNext = z->pNext;
free(z);
z = NULL; //避免野指针
return true;
}
(7)排序
void sort_list(pLNode pHead) //排序
{
int i, j, t;
int len = length_list(pHead);
pLNode p;
pLNode q;
if (is_empty(pHead) == false)
{
for (i = 0, p = pHead->pNext; i < len - 1; i++, p = p->pNext)
{
for (j = i + 1, q = p->pNext; j < len; j++, q = q->pNext)
{
if (p->data < q->data) //类似于数组中的:a[i] < a[j]
{
t = p->data; //类似于数组中的:t = a[i]
p->data = q->data; //类似于数组中的:a[i] = a[j]
q->data = t; //类似于数组中的:a[j] = t
}
}
}
}
else
{
printf("链表为空\n");
}
}
4.优缺点
(1)链表的优点
- 插入删除速度快
在插入和删除操作时,只需要修改被删节点上一节点的链接地址,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点。
- 内存利用率高,不会浪费内存
- 大小没有固定,拓展很灵活。
(2)链表的缺点
- 不能随机查找,必须从第一个开始遍历,查找效率低