关于顺序表
顺序表有一个大缺陷:数据必须连续存放
顺序表的优势是:
1.是一个连续的物理空间,方便下标随机访问。在排序和二分查找上有优势。
顺序表的其他缺陷:
1.插入数据,空间不够时要增容,增容有性能消耗。
2.头部或中间位置插入删除数据,需要挪动数据,效率较低。
3.存在空间占用,有空间浪费现象(扩容往往是扩二倍空间,二倍后的空间不会全部使用),不能按需申请和释放空间。
基于顺序表的缺点,链表结构就被设计出来。
我们将链表中一个储存数据的单元称为节点/结点
链表基本结构的声明:
typedef struct SinListNode
{
SLTDateType val;
struct SinListNode* next;
}SinListNode;
struct
SinListNode
* next;就是这个结构体的指针,结构体的中的指针成员可以指向这个结构体,这是链表的关键
友情提示:在实现数据结构时,因为自由度很高,变量和函数名要按照某个标准来,否则刚学习,容易弄混不统一代码所代表的含义
一个储存数据的单元由储存数据的变量和指向下一个数据的指针构成:
这是一个逻辑结构,在物理上是没有箭头的,在实际中指针存储了下一个链表空间的地址。
逻辑结构是我们方便理解,想象出来的,物理结构才是实际的结构。
可以通过对指针指向位置的改变,随意改变链表的顺序。
上述结构是一个单链表(Single List),之后还有其他链表结构,先介绍最基础的单链表
单链表也有一个大问题:不能通过第二个节点找第一个节点。由此又产生了双向链表,之后再学。
完成链表的打印:
提问:链表需要断言吗?
答案是不需要,因为链表在不存储数据的情况下是为空的,并不是一定不能为空。
打印函数
//声明部分省略
void SinListPrint(SinListNode* phead)
{
SinListNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->val);
cur = cur->next;
//在第一个节点的指针部分,存储了下一个节点的地址
}
printf("NULL\n");
}
下面进行尾插函数的书写
(友情提醒,虽然链表理解起来很容易,但理解链表的书写并不容易,也可能因人而异,反正我在这上面吃了不少苦头)
首先创立一个节点来储存数据,创立节点是一个经常用到的功能,创建一个函数来建立节点
//函数声明部分省略
SinListNode* BuySinListNode(SLTDateType x)
//建立节点
{
SinListNode* newnode = (SinListNode*)malloc(sizeof(SinListNode));
//malloc开辟的空间在堆区,不会因为函数结束而销毁
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
else
{
newnode->val = x;
newnode->next = NULL;
}
return newnode;
//这里的newnode是创立节点的地址,这很重要!
}
继续尾插函数
void SinListPushBack(SinListNode** head, SLTDateType x)
//如果不传地址值,head只是phead的拷贝,对head赋值不会影响phead,phead中的值一直是NULL
{
SinListNode* newnode = BuySinListNode(x);
//创立节点
if (*head == NULL)
//如果头为空,则没有数据储存,将节点的地址给head保证可以找到数据即可
{
*head = newnode;
}
else
{
SinListNode* tail = *head;
//不能使用head进行是否为空的查找,*head会实际改变phead指向的空间
while (tail->next != NULL)
//从头开始寻找链表的结尾,找到后进行新节点的链接
{
tail = tail->next;
//如果写成*head = (*head)->next,phead的值就会由第一个节点的地址变为下一个节点的地址
}
tail->next = newnode;
//链接新节点
}
}
必须找到newnode'位置处才能通过newnode'->next完成对newnode''位置的链接
单链表的缺陷:只能通过从头找的方式找到尾节点的位置
下面进行头插函数的书写
void SinListPushFront(SinListNode** pphead, SLTDateType x)
{
SinListNode* newnode = BuySinListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
视情况而定,在顺序表或者链表或者之后的某个知识感觉到难度都是正常的,这个世界上天才没有那么多,如果始终没有理解,那一定是之前的某个知识点没有理解透彻,即使之前的学习中都理解透彻了,数据结构的理解仍然会存在疑问,就我的亲身经历来讲,是适应数据结构的模式后,才有融会贯通的感觉。知识会平等的对待我们,没有任何知识是永远学不会的。
下面进行尾删的书写:(小技巧,前期学习可以通过画图来加深理解)
尾删的难点在找到最后的节点,完成空间释放后,没法把上一个空间置为NULL。解决方案是再创建一个变量(prve),加入新的条件后,会有新的情况产生,这是需要重新分析。能力就是在一次次犯错中提升的,这是编程学习的必经之路。
因为最后要通过新创建的变量(prve)将新链表的尾置为NULL,所以新创建的变量不能为NULL(要用prve->改变地址),这时就可以分为三种情况了,没有节点,一个节点,两个以上节点。
void SinListPopBack(SinListNode** pphead)
{
assert(pphead);
if (*pphead == NULL)
{
return;
}
else if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SinListNode* cur = *pphead;
SinListNode* prve = NULL;
//还有一种思路是cur->text->text != NULL直接判断后面的是否为空,然后free(cur->next);cur->text = NULL;
while (cur->next != NULL)
{
prve = cur;
cur = cur->next;
}
free(cur);
//也可以free(prve->next);
cur = NULL;
prve->next = NULL;
}
}
下面进行头删的书写:
头删的代码比较简单,这是链表的特点决定的,但也要考虑两种情况,其一,节点数为0,其二节点数不为0,也就是正常情况
void SinListPopFront(SinListNode** pphead)
{
assert(pphead);
if (*pphead == NULL)
{
return;
}
else
{
SinListNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
}
下面介绍链表的查找:
查找函数较简单,因为链表没有下标,只能返回地址,拿到了地址,就意味着可以对值进行修改
SinListNode* SinListFind(SinListNode* phead, SLTDateType x)
{
while (phead != NULL && phead->val != x)
//只有在链表结束或者找到查找的值时才停下
{
phead = phead->next;
}
if (phead != NULL)
//判断是因为结束停下,还是因为找到值停下
{
return phead;
}
else
{
return NULL;
}
}
基于查找函数的结果,写对链表目标地址前一个节点空间插入数据的函数
void SinListInsert(SinListNode** pphead, SinListNode* pos, SLTDateType x)
{
assert(pphead);
//pos不需要断言,在测试函数的过程中,pos不为NULL时才会进入插入函数
if (*pphead == pos)
{
SinListNode* cur = *pphead;
//可以使用之前的首插函数
*pphead = BuySinListNode(x);
(*pphead)->next = cur;
}
else
{
SinListNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SinListNode* newnode = BuySinListNode(x);
newnode->next = pos;
prev->next = newnode;
}
}
测试函数:
SinListNode* pos = SinListFind(phead, 3);
if (pos)
{
SinListInsert(&phead, pos, 30);
}
SinListPrint(phead);
pos = SinListFind(phead, 1);
//将函数中的首插部分测试一下
if (pos)
{
SinListInsert(&phead, pos, 30);
}
SinListPrint(phead);
之后写在pos之后位置插入数据的函数
void SinListInsertAfter(SinListNode* pos, SLTDateType x)
{
SinListNode* after = pos->next;
pos->next = BuySinListNode(x);
pos->next->next = after;
}
同样测试一下。
下面进行任意位置删除函数的书写,需要注意的就是不同情况的考虑,基本逻辑是找到pos前一个位置的地址,保存pos后面的地址,然后进行赋值,需要注意如果是第一个节点,没法找前一个,这时就需要考虑额外情况了。
void SinListErase(SinListNode** pphead, SinListNode* pos)
{
assert(pphead);
if (*pphead == pos)
{
*pphead = pos->next;
free(pos);
pos = NULL;
}
else
{
SinListNode* tmp = pos->next;
SinListNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = tmp;
free(pos);
pos = NULL;
}
}
总结:单链表适合头删,头插
实现上述链表的代码:
test.c中:
#include"SList.h"
void SinListtest1()
{
SinListNode* phead = NULL;
//p代表指针
SinListNode* n1 = (SinListNode*)malloc(sizeof(SinListNode));
if (n1 == NULL)
{
return;
}
SinListNode* n2 = (SinListNode*)malloc(sizeof(SinListNode));
if (n2 == NULL)
{
return;
}
SinListNode* n3 = (SinListNode*)malloc(sizeof(SinListNode));
if (n3 == NULL)
{
return;
}
n1->val = 1;
n2->val = 2;
n3->val = 3;
n1->next = n2;
n2->next = n3;
n3->next = NULL;
phead = n1;
SinListPrint(phead);
}
void SinListtest2()
{
SinListNode* phead = NULL;
SinListPushBack(&phead, 1);
SinListPushBack(&phead, 2);
SinListPushBack(&phead, 3);
SinListPushBack(&phead, 4);
SinListPushFront(&phead, 0);
SinListPrint(phead);
}
void SinListtest3()
{
SinListNode* phead = NULL;
SinListPushBack(&phead, 1);
SinListPushBack(&phead, 2);
SinListPushBack(&phead, 3);
SinListPushBack(&phead, 4);
SinListPopBack(&phead);
SinListPopBack(&phead);
SinListPopBack(&phead);
SinListPopBack(&phead);
SinListPrint(phead);
}
void SinListtest4()
{
SinListNode* phead = NULL;
SinListPushBack(&phead, 1);
SinListPushBack(&phead, 2);
SinListPushBack(&phead, 3);
SinListPushBack(&phead, 4);
SinListNode* pos = SinListFind(phead, 3);
if (pos)
{
pos->val *= 10;
}
SinListPrint(phead);
printf("%p\n", SinListFind(phead, 3));
printf("%p\n", SinListFind(phead, 2));
printf("%p\n", SinListFind(phead, 0));
}
void SinListtest5()
{
SinListNode* phead = NULL;
SinListPushBack(&phead, 1);
SinListPushBack(&phead, 2);
SinListPushBack(&phead, 3);
SinListPushBack(&phead, 4);
SinListNode* pos = SinListFind(phead, 3);
if (pos)
{
SinListInsert(&phead, pos, 30);
}
SinListPrint(phead);
pos = SinListFind(phead, 1);
if (pos)
{
SinListInsert(&phead, pos, 30);
}
SinListPrint(phead);
}
void SinListtest6()
{
SinListNode* phead = NULL;
SinListPushBack(&phead, 1);
SinListPushBack(&phead, 2);
SinListPushBack(&phead, 3);
SinListPushBack(&phead, 4);
SinListNode* pos = SinListFind(phead, 3);
if (pos)
{
SinListInsertAfter(pos, 30);
}
SinListPrint(phead);
}
void SinListtest7()
{
SinListNode* phead = NULL;
SinListPushBack(&phead, 1);
SinListPushBack(&phead, 2);
SinListPushBack(&phead, 3);
SinListPushBack(&phead, 4);
SinListPrint(phead);
SinListNode* pos = SinListFind(phead, 3);
if (pos)
{
SinListErase(&phead, pos);
}
pos = SinListFind(phead, 1);
if (pos)
{
SinListErase(&phead, pos);
}
SinListPrint(phead);
}
int main()
{
SinListtest7();
return 0;
}
SList.h中
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SinListNode
{
SLTDateType val;
struct SinListNode* next;
}SinListNode;
void SinListPrint(SinListNode* head);
SinListNode* BuySinListNode(SLTDateType x);
void SinListPushBack(SinListNode** head, SLTDateType x);
void SinListPushFront(SinListNode** head, SLTDateType x);
void SinListPopFront(SinListNode** head);
void SinListPopBack(SinListNode** head);
SinListNode* SinListFind(SinListNode* head, SLTDateType x);
void SinListInsert(SinListNode** pphead, SinListNode* pos, SLTDateType x);
void SinListErase(SinListNode** pphead, SinListNode* pos);
void SinListInsertAfter(SinListNode* pos, SLTDateType x);
void SinListEraseAfter(SinListNode* pos);
void SinListDestroy(SinListNode** pphead);
SList.c中
#include"SList.h"
SinListNode* BuySinListNode(SLTDateType x)
//建立节点
{
SinListNode* newnode = (SinListNode*)malloc(sizeof(SinListNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
else
{
newnode->val = x;
newnode->next = NULL;
}
return newnode;
}
void SinListPrint(SinListNode* phead)
{
SinListNode* cur = phead;
//p代表指针
while (cur != NULL)
{
printf("%d->", cur->val);
cur = cur->next;
}
printf("NULL\n");
}
void SinListPushBack(SinListNode** pphead, SLTDateType x)
{
assert(pphead);
SinListNode* newnode = BuySinListNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SinListNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SinListPushFront(SinListNode** pphead, SLTDateType x)
{
assert(pphead);
SinListNode* newnode = BuySinListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SinListPopBack(SinListNode** pphead)
{
assert(pphead);
if (*pphead == NULL)
{
return;
}
else if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SinListNode* cur = *pphead;
SinListNode* prve = NULL;
while (cur->next != NULL)
{
prve = cur;
cur = cur->next;
}
free(cur);
cur = NULL;
prve->next = NULL;
}
}
void SinListPopFront(SinListNode** pphead)
{
assert(pphead);
if (*pphead == NULL)
{
return;
}
else
{
SinListNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
}
SinListNode* SinListFind(SinListNode* phead, SLTDateType x)
{
while (phead != NULL && phead->val != x)
{
phead = phead->next;
}
if (phead != NULL)
{
return phead;
}
else
{
return NULL;
}
}
void SinListInsert(SinListNode** pphead, SinListNode* pos, SLTDateType x)
{
assert(pphead);
if (*pphead == pos)
{
SinListNode* cur = *pphead;
*pphead = BuySinListNode(x);
(*pphead)->next = cur;
}
else
{
SinListNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SinListNode* newnode = BuySinListNode(x);
newnode->next = pos;
prev->next = newnode;
}
}
void SinListErase(SinListNode** pphead, SinListNode* pos)
{
assert(pphead);
if (*pphead == pos)
{
*pphead = pos->next;
free(pos);
pos = NULL;
}
else
{
SinListNode* tmp = pos->next;
SinListNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = tmp;
free(pos);
pos = NULL;
}
}
void SinListInsertAfter(SinListNode* pos, SLTDateType x)
{
SinListNode* after = pos->next;
pos->next = BuySinListNode(x);
pos->next->next = after;
}
void SinListEraseAfter(SinListNode* pos)
{
assert(pos);
SinListNode* next = pos->next;
if (pos->next != NULL)
{
pos->next = next->next;
free(next);
}
}
void SinListDestroy(SinListNode** pphead)
{
assert(pphead);
SinListNode* cur = *pphead;
while (cur)
{
SinListNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}