赞
踩
在之前我们已经学习了顺序表,顺序表有一定的缺陷,比如需要扩容,在插入和删除时需要挪动数据等问题,在此基础上我们可以学习一一种新的数据结构-单链表,相对来说它可以按需申请空间,并且不需要挪动数据。
我们在下面的代码封装了三个文件,分别是:
目录
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素 +指针(下一个元素的地址),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
链表的结构如下:
由于链表支持存储数据的空间不连续,所以我们要存储一部分数据,我们在存储了一个数据后想要找到下一个数据,这个时候就需要在存储数据的同时存储下一个数据的地址,即一个单链表的元素(结点)需要包含两个内容:数据和下一个数据的地址,类似于下图:
由于一个结点包含多个数据并且数据的类型不同,所以在这里为了描述单链表的一个结点,我们使用结构体 。
- typedef int SLTDataType; //为使程序有扩展性,在想要改变数据类型时,在这里可以直接改变
-
- typedef struct SListNode
- {
- SLTDataType data;
- struct SListNode* next;
- }SLTNode;
- //使用类型重定义
以下是我们欲实现的和单链表有关的函数:
- SLTNode* BuySLTNode(SLTDataType x);
- //创建一个有n个结点的链表
- SLTNode* CreateSList(int n);
-
- //打印链表
- void SLTPrint(SLTNode* phead);
-
- //单链表尾插
- void SLTPushBack(SLTNode** phead, SLTDataType x);
-
- //单链表尾删
- void SLTPopBack(SLTNode** phead);
-
- //单链表的头插
- void SLTPushFront(SLTNode** phead, SLTDataType x);
-
- //单链表的头删
- void SLTPopFront(SLTNode** phead);
-
- //单链表的查找
- SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
-
- //单链表在pos位置之后插入x
- void SListInsertAfter(SLTNode* pos, SLTDataType x);
-
- //单链表在pos位置之前插入x
- void SListInsert(SLTNode** pphead,SLTNode* pos, SLTDataType x);
-
- //删除pos位置的后一个元素
- void SLTEraseAfter(SLTNode* pos);
- //删除pos位置
- void SLTErase(SLTNode** pphead, SLTNode* pos);
-
- //销毁
- void SLTDestroy(SLTNode** pphead);
由于单链表需要满足按需申请和释放空间,在这里我们使用malloc申请空间,对malloc函数不了解的朋友可以去上一篇博客学习。
- SLTNode* BuySLTNode(SLTDataType x)
- {
- SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
- if (newnode == NULL)
- {
- perror("malloc fail");
- exit(-1);
- }
-
- newnode->data = x;
- newnode->next = NULL;
-
- return newnode;
- }
单链表是按需申请空间,我们想要创建一个有n个结点的单链表,对于单链表而言,单链表之间的每个结点需要链接起来,所以在这里我们使用CreateLisst函数来实现创建一个链表的功能。
CreateLisst函数主要实现两个功能:1.调用BuySLTNode函数申请一个结点的空间。2.将所有的结点链接起来。
- SLTNode* CreateSList(int n)
- {
- SLTNode* phead = NULL, * ptail = NULL;
- for (int i = 0; i < n; i++)
- {
- SLTNode* newnode = BuySLTNode(i);
- if (phead == NULL)
- {
- phead = ptail = newnode;
- }
- else
- {
- ptail->next = newnode;
- ptail = newnode;
- }
- }
- return phead;
- }
我们在完成链表的增删查改之后想要观察链表是否正确最直观的方式是将他直接打印下来,打印一个链表,我们需要遍历这个链表,对链表的遍历,我们需要一个指针,一般不使用头指针直接遍历,所以我们需要将phead的值赋给另一个指针cur,由它来遍历,链表的结束条件是链表的下一个指针指向空,所以当cur为空的时侯,遍历结束。
其次我们需要注意的是当链表为空的时候我们是否需要单独处理?答案是不需要,空链表也可以打印。
- void SLTPrint(SLTNode* phead)
- {
- SLTNode* cur = phead;
- while (cur)
- {
- printf("%d->", cur->data);
- cur = cur->next;
- }
- printf("NULL\n");
- }
我们写完了创建结点函数和创建一个链表的函数之后,我们可以对他进行测试,看看函数功能实现是否正确,避免在最后代码一起运行时执行出错但不知道那个函数出错的情况。
- void TestSList1()
- {
- SLTNode* plist = NULL;
- plist = CreateSList(10); //创建一个有10个结点的链表
- SLTPrint(plist);
- }
-
- int main()
- {
- TestSList1();
- return 0;
- }
代码正常运行,说明我们之前写的三个函数没有问题。
我们要进行单链表尾插操作,在写这个函数时有些人都会出错,在这里有两种经典的错误写法,我们先对这个错误写法进行分析再写出正确的写法。
- //错误示例1
- void SLTPushBack(SLTNode* phead, SLTDataType x)
- {
- SLTNode* newnode = BuySLTNode(x);
- SLTNode* tail = phead;
- while (tail)
- {
- tail = tail->next;
- }
- tail = newnode;
- }
上面代码的问题在哪里?我们通过链表的逻辑结构可以看出来。
执行上述代码之前链表的的逻辑结构(假设由三个结点):
执行上述代码之后的逻辑结构如下:
我们发现执行上述代码并没有改变原来链表的结构,下面我们来解释原因,首先tail是一个局部变量,局部变量在函数调用完成时自动销毁,在上述代码中我们并没有改变链表的链接关系,只是让tail等于newnode,链表本身没有改变,其次没有考虑链表是空链表的情况,所以错误。
有些同学在写尾插函数时还会像下面一样写:
- //错误示例2
- void SLTPushBack(SLTNode* phead, SLTDataType x)
- {
- SLTNode* newnode = BuySLTNode(x);
- if (phead == NULL)
- {
- phead = newnode;
- }
- else
- {
- SLTNode* tail = phead;
- while (tail->next)
- {
- tail = tail->next;
- }
- tail->next = newnode;
- }
-
- }
上述的代码有什么问题呢?
当链表是空的时候,我们进行尾插时,链表的头结点也应该发生改变,而上述代码中phead的改变并不会影响plist,phead是形参,形参的改变并不会对实参造成影响,也就是说,即使在函数内部我们将phead的值改变,对于plist来说不会有任何变化,所以上述代码错误。
在上述代码中的错误主要是当链表为空时,phead改变不会对链表的头节点改变,所以我们这里需要想办法使phead改变时,plist也改变,由此我们想到了传指针的方式。
对于函数传参传的是指针时,传的是指针改变的就是指针指向的内容,所以这里我们想要改变plist,我们就要传plist的指针,plist本来就是一个指针,在这里我们需要传的是二级指针。
-
- void SLTPushBack(SLTNode** pphead, SLTDataType x)
- {
- SLTNode* newnode = BuySLTNode(x);
- if (*pphead == NULL)
- {
- *pphead = newnode;
- }
- else
- {
- SLTNode* tail = *pphead;
- while (tail->next)
- {
- tail = tail->next;
- }
- tail->next = newnode;
- }
- }
对于单链表的尾删,我们有两种方式。
我们要对单链表进行尾删操作,需要注意的是,当链表为空的时候,不能进行尾删。
我们需要注意的是:尾删也有可能会改变链表的头结点(即链表只有一个结点的时候),如果要改变链表的头节点,我们就需要传一个二级指针。
我们可以画出大致的逻辑结构来写代码。
除此之外,我们需要考虑当只有一个结点的情况:
我们要对单链表进行尾删操作,需要记录要删除元素的前一个位置的指针,所以在这里我们采用双指针的方式。
- void SLTPopBack1(SLTNode** pphead)
- {
- assert(*pphead);
- if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- else
- {
- SLTNode* prev = NULL;
- SLTNode* tail = *pphead;
- while (tail->next)
- {
- prev = tail;
- tail = tail->next;
- }
- free(tail);
- prev->next = NULL;
- }
- }
- void SLTPopBack(SLTNode** pphead)
- {
- assert(*pphead);
- SLTNode* ptail = *pphead;
- if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- else
- {
- while (ptail->next->next)
- {
- ptail = ptail->next;
- }
-
- free(ptail->next);
- ptail->next = NULL;
- }
- }
我们又完成了尾插和尾删函数的编写,在这里我们可以测试一下两个函数是否正确。
- void TestSList2()
- {
- SLTNode* plist = NULL;
- SLTPushBack(&plist, 1);
- SLTPushBack(&plist, 6);
- SLTPushBack(&plist, 7);
- SLTPushBack(&plist, 7);
- SLTPushBack(&plist, 6);
- SLTPrint(plist);
-
- SLTPopBack1(&plist);
- SLTPopBack(&plist);
- SLTPopBack1(&plist);
-
- SLTPrint(plist);
-
- }
-
- int main()
- {
- TestSList2();
- return 0;
- }
我们要对单链表进行头插(即在第一个结点前插入元素),可以先画出图来分析:
在头插函数中不需要判空,即使链表为空,我们也能进行头插,由于在头插的过程中需要不断地改变链表的头结点,所以我们在传参的时候需要将链表指针的地址传进来。
- void SLTPushFront(SLTNode** pphead, SLTDataType x)
- {
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = *pphead;
- *pphead = newnode;
- }
我们要对单链表进行头删(即删除第一个结点的元素),可以先画出图来分析:
对链表进行头删操作,需要对链表的头结点进行改变,所以在这里我们传参的时候传的是二级指针,其次如果链表为空那么头删操作不能进行,所以在这里我们需要判空。
- void SLTPopFront(SLTNode** pphead)
- {
- assert(*pphead);
- SLTNode* next = (*pphead)->next;
- free(*pphead);
- *pphead = next;
- }
我们把头插函数和头删函数代码写完了,可以通过测试函数来看上面两个函数是否有问题。
- void TestSList3()
- {
- SLTNode* plist = NULL;
- SLTPushFront(&plist, 1);
- SLTPushFront(&plist, 6);
- SLTPushFront(&plist, 7);
- SLTPushFront(&plist, 7);
- SLTPushFront(&plist, 6);
- SLTPrint(plist);
-
- SLTPopFront(&plist);
- SLTPopFront(&plist);
- SLTPopFront(&plist);
-
- SLTPrint(plist);
-
- }
-
- int main()
- {
- TestSList3();
- return 0;
- }
我们在某些时候想要查找某个值在链表中的位置,这个时候就需要有查找函数了。
注意:空链表是可以查找的,所在在这里我们不需要判空,其次我们只是对链表进行查找操作,不会改变链表,所以也不需要传二级指针。
在链表中进行查找是否存在x,如果存在返回他的指针,如果不存在返回空指针。
- SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
- {
- SLTNode* cur = phead;
- while (cur)
- {
- if (cur->data == x)
- {
- return cur;
- }
- cur = cur->next;
- }
- return NULL;
- }
在这个函数中,我们主要需要注意以下两个问题:
- //单链表在pos位置之后插入x
- void SListInsertAfter(SLTNode* pos, SLTDataType x)
- {
- assert(pos);
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = pos->next;
- pos->next = newnode;
- }
在pos之前插入x,我们需要考虑特殊情况,即pos是头结点的情况,在这种情况下,如果在pos之前插入x,此时头节点会改变,我们在传参的时候需要传二级指针,其次在这里我们需要对pos判空,在查找函数中,pos如果返回的是空,那么即代表没有查找到,既然没有查找到,我们也不需要在这里进行插入操作。
- void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
- {
- assert(pos);
- if (*pphead == NULL)
- {
- SLTPushFront(pphead, x);
- }
- else
- {
- SLTNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
-
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = pos;
- prev->next = newnode;
- }
- }
- void SLTEraseAfter(SLTNode* pos)
- {
- assert(pos);
- if (pos->next == NULL)
- {
- return;
- }
- else
- {
- SLTNode* nextNode = pos->next;
- pos->next = nextNode->next;
- free(nextNode);
- }
- }
画出删除pos位置可能的情况,大概有以下两种:
我们要着重考虑pos位置是头结点的情况,如果pos位置是头结点,那么整个链表的头结点要发生改变,所以这里我们要传二级指针。
- void SLTErase(SLTNode** pphead, SLTNode* pos)
- {
- assert(pos);
- if (pos == *pphead)
- {
- SLTPopFront(pphead); //头删函数的复用
- }
- else
- {
- SLTNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
-
- prev->next = pos->next;
- free(pos);
- }
- }
- void TestSList4()
- {
- SLTNode* plist = NULL;
- SLTPushBack(&plist, 1);
- SLTPushBack(&plist, 2);
- SLTPushBack(&plist, 3);
- SLTPushBack(&plist, 4);
- SLTPushBack(&plist, 5);
- SLTPrint(plist);
-
- SLTNode* p = SLTFind(plist, 3);
- SLTEraseAfter(p);
- SLTPrint(plist);
-
- p = SLTFind(plist, 3);
- SLTErase(&plist, p);
- SLTPrint(plist);
-
- }
-
-
- int main()
- {
- TestSList4();
- return 0;
- }
我们在之前的单链表的结点都是在堆区申请的,在堆区申请的空间需要手动释放,所以我们要写一个释放函数。
- void SLTDestroy(SLTNode** pphead)
- {
- SLTNode* cur = *pphead;
- while (cur)
- {
- SLTNode* next = cur->next;
- free(cur);
- cur = next;
- }
- *pphead = NULL;
- }
- #include "SList.h"
-
- void TestSList1()
- {
- SLTNode* plist = NULL;
- plist = CreateSList(10); //创建一个有10个结点的链表
- SLTPrint(plist);
- }
-
- void TestSList2()
- {
- SLTNode* plist = NULL;
- SLTPushBack(&plist, 1);
- SLTPushBack(&plist, 6);
- SLTPushBack(&plist, 7);
- SLTPushBack(&plist, 7);
- SLTPushBack(&plist, 6);
- SLTPrint(plist);
-
- SLTPopBack1(&plist);
- SLTPopBack(&plist);
- SLTPopBack1(&plist);
-
- SLTPrint(plist);
-
- }
-
- void TestSList3()
- {
- SLTNode* plist = NULL;
- SLTPushFront(&plist, 1);
- SLTPushFront(&plist, 6);
- SLTPushFront(&plist, 7);
- SLTPushFront(&plist, 7);
- SLTPushFront(&plist, 6);
- SLTPrint(plist);
-
- SLTPopFront(&plist);
- SLTPopFront(&plist);
- SLTPopFront(&plist);
-
- SLTPrint(plist);
-
- }
-
- void TestSList4()
- {
- SLTNode* plist = NULL;
- SLTPushBack(&plist, 1);
- SLTPushBack(&plist, 2);
- SLTPushBack(&plist, 3);
- SLTPushBack(&plist, 4);
- SLTPushBack(&plist, 5);
- SLTPrint(plist);
-
- SLTNode* p = SLTFind(plist, 3);
- SLTEraseAfter(p);
- SLTPrint(plist);
-
- p = SLTFind(plist, 3);
- SLTErase(&plist, p);
- SLTPrint(plist);
-
- }
-
-
- int main()
- {
- TestSList4();
- return 0;
- }
- #pragma once
- #include<stdio.h>
- #include<stdlib.h>
- #include<string.h>
- #include<assert.h>
-
-
- typedef int SLTDataType; //为使程序有扩展性,在想要改变数据类型时,在这里可以直接改变
-
- typedef struct SListNode
- {
- SLTDataType data;
- struct SListNode* next;
- }SLTNode;
-
- SLTNode* BuySLTNode(SLTDataType x);
- //创建一个有n个结点的链表
- SLTNode* CreateSList(int n);
-
- //打印链表
- void SLTPrint(SLTNode* phead);
-
- //单链表尾插
- void SLTPushBack(SLTNode** phead, SLTDataType x);
-
- //单链表尾删
- void SLTPopBack(SLTNode** phead);
-
- //单链表的头插
- void SLTPushFront(SLTNode** phead, SLTDataType x);
-
- //单链表的头删
- void SLTPopFront(SLTNode** phead);
-
- //单链表的查找
- SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
-
- //单链表在pos位置之后插入x
- void SListInsertAfter(SLTNode* pos, SLTDataType x);
-
- //单链表在pos位置之前插入x
- void SListInsert(SLTNode** pphead,SLTNode* pos, SLTDataType x);
-
- //删除pos位置的后一个元素
- void SLTEraseAfter(SLTNode* pos);
- //删除pos位置
- void SLTErase(SLTNode** pphead, SLTNode* pos);
-
- //销毁
- void SLTDestroy(SLTNode** pphead);
- #include "SList.h"
-
-
- SLTNode* BuySLTNode(SLTDataType x)
- {
- SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
- if (newnode == NULL)
- {
- perror("malloc fail");
- exit(-1);
- }
-
- newnode->data = x;
- newnode->next = NULL;
-
- return newnode;
- }
-
- SLTNode* CreateSList(int n)
- {
- SLTNode* phead = NULL, * ptail = NULL;
- for (int i = 0; i < n; i++)
- {
- SLTNode* newnode = BuySLTNode(i);
- if (phead == NULL)
- {
- phead = ptail = newnode;
- }
- else
- {
- ptail->next = newnode;
- ptail = newnode;
- }
- }
- return phead;
- }
-
- void SLTPrint(SLTNode* phead)
- {
- SLTNode* cur = phead;
- while (cur)
- {
- printf("%d->", cur->data);
- cur = cur->next;
- }
- printf("NULL\n");
- }
-
- void SLTPushBack(SLTNode** pphead, SLTDataType x)
- {
- SLTNode* newnode = BuySLTNode(x);
- if (*pphead == NULL)
- {
- *pphead = newnode;
- }
- else
- {
- SLTNode* tail = *pphead;
- while (tail->next)
- {
- tail = tail->next;
- }
- tail->next = newnode;
- }
- }
-
-
-
- void SLTPopBack1(SLTNode** pphead)
- {
- assert(*pphead);
- if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- else
- {
- SLTNode* prev = NULL;
- SLTNode* tail = *pphead;
- while (tail->next)
- {
- prev = tail;
- tail = tail->next;
- }
- free(tail);
- prev->next = NULL;
- }
- }
-
-
- void SLTPopBack(SLTNode** pphead)
- {
- assert(*pphead);
- SLTNode* ptail = *pphead;
- if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- else
- {
- while (ptail->next->next)
- {
- ptail = ptail->next;
- }
-
- free(ptail->next);
- ptail->next = NULL;
- }
- }
-
- void SLTPushFront(SLTNode** pphead, SLTDataType x)
- {
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = *pphead;
- *pphead = newnode;
- }
-
- void SLTPopFront(SLTNode** pphead)
- {
- assert(*pphead);
- SLTNode* next = (*pphead)->next;
- free(*pphead);
- *pphead = next;
- }
-
- SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
- {
- SLTNode* cur = phead;
- while (cur)
- {
- if (cur->data == x)
- {
- return cur;
- }
- cur = cur->next;
- }
- return NULL;
- }
-
- //单链表在pos位置之后插入x
- void SListInsertAfter(SLTNode* pos, SLTDataType x)
- {
- assert(pos);
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = pos->next;
- pos->next = newnode;
- }
-
- void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
- {
- assert(pos);
- if (*pphead == NULL)
- {
- SLTPushFront(pphead, x);
- }
- else
- {
- SLTNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
-
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = pos;
- prev->next = newnode;
- }
- }
-
-
-
- void SLTEraseAfter(SLTNode* pos)
- {
- assert(pos);
- if (pos->next == NULL)
- {
- return;
- }
- else
- {
- SLTNode* nextNode = pos->next;
- pos->next = nextNode->next;
- free(nextNode);
- }
- }
-
- void SLTErase(SLTNode** pphead, SLTNode* pos)
- {
- assert(pos);
- if (pos == *pphead)
- {
- SLTPopFront(pphead); //头删函数的复用
- }
- else
- {
- SLTNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
-
- prev->next = pos->next;
- free(pos);
- }
- }
-
- void SLTDestroy(SLTNode** pphead)
- {
- SLTNode* cur = *pphead;
- while (cur)
- {
- SLTNode* next = cur->next;
- free(cur);
- cur = next;
- }
- *pphead = NULL;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。