目录
就在今天哈登打出了很好的表现,自从来到费城,腿筋的伤势、角色的改变等等,从初三了解开始,他的比赛也是看一场少一场,小小致敬一下!
放在文章的前面,也是在激励自己。希望我也可以像他一样,向他致敬!!!
1.关于顺序表的问题及思考
(1)顺序表从中间或是头部插入数据,时间复杂度为O(n)。
(2)增容需要申请空间,扩容有可能会拷贝数据,释放旧空间,会有一些消耗。
(3)增加的空间会产生不增不够用,增了会浪费的情况。
如何解决这样的问题?可以看看链表的结构。
2.链表的概念
链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针依次连接来实现的。
所以对于链表的结构进行理解,单链表就像是一列火车,火车每一节车厢之间的连接部分就像是指针的指向下一个结点,但是也有一点不同的是火车在运行的时候,其实每一个车厢在物理上是连续的。但是链表的结点一般是在堆上申请来的空间,有可能物理空间是不连续的,只是在逻辑上是连续的。
3.单链表的实现
链表的分类有很多,我们现在一起看看单链表的基本操作的实现。
(1)新建一个工程
SList.h(单链表结点的定义、头文件、函数的声明)
SList.c(接口函数的具体实现)
test.c(测试)
(2)结点的定义
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
(3) 打印函数
单链表中数据的打印就像是单链表开始学习的开胃赛,在吃大餐前,先和大家上一点凉菜。
void SLPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->",cur->data);
cur = cur->next;
}
printf("NULL\n");
}
通过找尾的方式来遍历单链表用于打印每个元素。
(4)增
在进行增加数据的操作之前,需要创建新的结点,我们可以先写一个这样的函数便于头插和尾插时可以复用。这个函数返回创建新的节点。
SLTNode* BuyLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
1.尾插
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuyLTNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
这里难以理解的是为什么函数的形参是二级指针?
这是因为当链表为空的时候,头指针指向NULL,在插入数据的时候需要头指针指向新的结点,也就是改变一级指针,所以就需要二级指针作为形参来接收。(如果用一级指针是无法对于头指针做出实际的改变的,形参是实参的一份临时拷贝,对于形参的改变不影响实参的改变)。
那么头插也是一个道理
2.头插
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
只实现头插和尾插当然是不够的我们还可以是实现在指定位置pos的前后增加数据。
3.在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLPushFront(pphead,x);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = BuyLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
4.在pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
这里使用一级指针是因为在pos之后插入数据证明链表不为空,无需对链表头指针进行改变。只是对于结构体的尾节点修改而已。
(5)删
1.尾删
void SLPOpBack(SLTNode** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
2.头删
void SLPOpFront(SLTNode** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tmp = *pphead;
*pphead = tmp->next;
free(tmp);
}
}
3.删除pos位置的值
//删除pos位置的值
void SLErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(*pphead);
if (pos == *pphead)
{
SLPOpFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
4.删除pos位置之后的值
void SLEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* next = pos->next;
next->next = pos->next;
free(next);
}
(6)查和改
单链表中数据的查找相比于增和删容易一些。就是通过遍历的方式,找到返回该结点,找不到就返回NULL。
SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
}
return NULL;
}
对于单链表数据的更改,可以和单链表的查找一起配合使用,想要修改哪个数据,找到之后就可以进行修改。类似于这样:
SLTNode* pos = SLFind(plist,4);
if (pos)
{
SLInsertAfter( pos, 1);
}
如果返回结点不为空,证明找到了,在进行修改(你想要头插、尾插、头删、尾删就是你自己的事了)。例如上面的例子,我就在pos之后插入了1。
(7)销毁链表
链表的销毁不可以直接free(phead),链表存储数据是物理上不连续的,所以就挨个销毁。
void SLTDestory(SLTNode** pphead)
{
assert(pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
关于test.c文件就用于测试接口函数是否符合我们的预期。
#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"
void Test1()
{
SLTNode* plist = NULL;
SLPushFront(&plist, 1);
SLPushFront(&plist, 2);
SLPOpBack(&plist);
SLPushFront(&plist, 3);
SLPOpFront(&plist);
SLPushFront(&plist, 4);
SLPushBack(&plist,5);
SLTNode* pos = SLFind(plist,4);
if (pos)
{
SLInsertAfter( pos, 1);
}
pos = SLFind(plist,4);
if (pos)
{
SLErase(&plist,pos);
}
SLPrint(plist);
SLTDestory(&plist);
}
int main()
{
Test1();
return 0;
}
结果如下:
SList.h如下:
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
void SLPrint(SLTNode* phead);
void SLPushFront(SLTNode** pphead,SLTDataType x);
void SLPushBack(SLTNode** pphead,SLTDataType x);
void SLPOpFront(SLTNode** pphead);
void SLPOpBack(SLTNode** pphead);
//单链表查找
SLTNode* SLFind(SLTNode* phead,SLTDataType x);
//在pos之前插入
void SLInsert(SLTNode** pphead,SLTNode* pos,SLTDataType x);
//在pos之后插入
void SLInsertAfter(SLTNode* pos,SLTDataType x);
//删除pos位置的值
void SLErase(SLTNode** pphead,SLTNode* pos);
//删除pos位置后面的值
void SLEraseAfter(SLTNode* pos);
void SLTDestory(SLTNode** pphead);
今天分享到这里,希望大家一起提高!!!