大家好,今天我们来学习数据结构中的顺序表与链表!源码在最后附上
首先我们先来认识一下顺序表:
**如上图所示:很多人会以为数组就是顺序表,顺序表就是数组,这种理解时错误的。
数组:
数组是相同数据类型的元素按一定顺序排列的的集合。数组中的元素存储在一个连续性的内存块中,并通过索引来访问。
简单的说,数组是在物理空间中连续存储的相同数据类型的元素的集合。
而顺序表:
顺序表,全名顺序存储结构,是线性表的一种。(线性表(linear list)是数据结构的一种,一个线性表是 n 个具有相同特性的数据元素的有限序列。线性表在逻辑上是线性结构,也就说是连续的一条直线,但是在物理结构上并不一定是连续的。常见的线性表:顺序表、链表、栈、队列、字符串等。)
顺序表不仅要求数据在逻辑上是连续的一条直线,还要求用一段物理地址连续的存储单元以存储表中数据元素,一般情况下采用数组存储。
总结:
顺序表是在计算机内存中以数组的形式保存的线性表,只能从头开始连续存储。
此处将「数组」理解为物理结构,「顺序表」理解为逻辑结构比较合理
我们可以用数组实现顺序表,但我们同样可以用数组实现二叉树、队列等结构,因此不能直接认为顺序表就是数组
在顺序表中我们可以定义表中元素个数、表空间大小等变量,在数组中是没有的
首先我们来学习一下顺序表的静态存储:
而顺序表的静态存储又存在明显的缺陷和不足:
不知道需要给确定的多少个空间,N给小了不够用,N给大了会浪费。
那么如何解决这个问题呢?
在这里我们使用malloc来动态开辟空间,再使用realloc来进行空间的管理。
顺序表的动态存储:
在这里我们要注意realloc扩容存在异地扩容和原地扩容俩种情况;
注意地址,当你realloc的空间没有那么大时,这里就是原地扩容:
注意地址,当你realloc的空间很大的时候,就会异地扩容:
这里先让我们简单的来实现顺序表的几个功能:(完整功能最后附上源码)
实现顺序表的头插,即往前边插进去数据:
实现顺序表中间插入数据:
实现顺序表的删除:
通过以上学习,我们大概了解了顺序表的一些基础知识,那么顺序表是否存在一些缺点呢?
顺序表的缺点:
1.尾部插入数据还不错,但是头部和中间插入删除数据,效率低下,需要挪动数据。
2.满了之后只能扩容,而扩容是有一定的消耗的,扩容一般是存在一定的空间浪费(假如空间100已经满了,扩容到200,只需要插入120个数据,那么就有80个浪费掉了)
3.一次扩的多了,可能浪费掉了;一次扩的少了,有需要频繁去扩容。
接下来让我们来学习链表:
链表:链表采用链式存储结构,其内存空间不连续
而链表分为单链表和双链表,我们先来学习单链表:
它是由一个一个结点结合在一起的,而每个节点的地址没有关联,都是随机的,东一个西一个。
注意单链表的最后一个结点指向NULL;
这里我们简单的定义一个单链表:
如何去访问链表里的各个结点呢:我们定义一个cur指针指向单链表的头节点,然后通过cur->next来依次访问剩下的结点,如果cur==NULL,说明链表访问完了。
接下来我们来实现单链表几个基本的功能:(完整源码结尾附上)
单链表的尾插:
这里需要额外注意:因为phead是plist的临时拷贝,使用要注意二级指针与一级指针的使用,当然不使用二级指针也是可以 ,也有不同的写法,这里这样写是让大家小心注意一下其中的大坑。
单链表的头插:
单链表的尾删:
需要注意,这里需要区分一个结点和多个结点的不同情况。
单链表的头删:
这里需要注意:我们的思路是把头节点的指向原本头结点的下一个结点,然后还需要free掉原本的头节点,这里会出现一种很常见的错误:
先free掉tmp后,就没办法再进行头结点的链接,丢失了地址。
而正确的写法:(大家注意区分)
关于链表还有一个知识点,就是哨兵位:
哨兵位就是在头节点的前面给它再加一个结点,让它充当头节点,但它不存储具体的数据,只是更方便进行头插头删等操作,哨兵位的含金量不是很高,有时候方便用来刷OJ题,这里大家简单了解下,有兴趣的朋友自己敲敲代码试一下把。
单链表我们就学到这里,接下来我们学习一下双链表:
这里我们先再来了解下链表有哪些分类:
我们注意到,单链表和双链表的区别是:单链表一个结点只能指向下一个结点,而双边表一个结点可以指向前一个结点也可以指向后一个结点。
而循环链表的特殊之处则是尾结点直接指向了头节点,实现了一个循环。
接下来我们先来比较一下俩种链表:
我们主要学习一下带头双向循环链表:
根据上面单链表的学习,我们简单写几个功能让加强大家对双向链表的理解:
带头双向循环链表的尾插:
我们可以明显感受到,双向循环链表的数据处理只需要几个简单的指向就可以简单完成;
带头双向循环链表的尾删:
知识的分享就结束了,接下来附上顺序表与链表的源码:(因为内容较多,所以不是很全,但有具体的框架,大家可以根据自己的需要进行补全)
顺序表:
SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;
int size; // 有效数据
int capacity; // 空间容量
}SL;
void SLInit(SL* psl);
void SLDestroy(SL* psl);
void SLPrint(SL* psl);
void SLCheckCapacity(SL* psl);
// 头尾插入删除
void SLPushBack(SL* psl, SLDataType x);
void SLPushFront(SL* psl, SLDataType x);
void SLPopBack(SL* psl);
void SLPopFront(SL* psl);
// 任意下标位置的插入删除
void SLInsert(SL* psl, int pos, SLDataType x);
void SLErase(SL* psl, int pos);
SeqList.c
#include"SeqList.h"
void SLInit(SL* psl)
{
assert(psl);
psl->a = NULL;
psl->size = 0;
psl->capacity = 0;
}
void SLDestroy(SL* psl)
{
assert(psl);
if (psl->a != NULL)
{
free(psl->a);
psl->a = NULL;
psl->size = 0;
psl->capacity = 0;
}
}
void SLPrint(SL* psl)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
void SLCheckCapacity(SL* psl)
{
assert(psl);
if (psl->size == psl->capacity)
{
int newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType)*newCapacity);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
psl->a = tmp;
psl->capacity = newCapacity;
}
}
// 10:37
void SLPushBack(SL* psl, SLDataType x)
{
assert(psl);
SLCheckCapacity(psl);
psl->a[psl->size] = x;
psl->size++;
}
void SLPushFront(SL* psl, SLDataType x)
{
assert(psl);
SLCheckCapacity(psl);
// 挪动数据
int end = psl->size - 1;
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[0] = x;
psl->size++;
}
void SLPopBack(SL* psl)
{
assert(psl);
// 空
// 温柔的检查
/*if (psl->size == 0)
{
return;
}*/
// 暴力检查
assert(psl->size > 0);
//psl->a[psl->size - 1] = -1;
psl->size--;
}
// 10:47
void SLPopFront(SL* psl)
{
assert(psl);
// 暴力检查
assert(psl->size > 0);
int begin = 1;
while (begin < psl->size)
{
psl->a[begin - 1] = psl->a[begin];
++begin;
}
psl->size--;
}
// 注意pos是下标
// size是数据个数,看做下标的话,他是最后一个数据的下一个位置
void SLInsert(SL* psl, int pos, SLDataType x)
{
assert(psl);
assert(pos >= 0 && pos <= psl->size);
SLCheckCapacity(psl);
// 挪动数据
int end = psl->size - 1;
while (end >= pos)
{
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[pos] = x;
psl->size++;
}
void SLErase(SL* psl, int pos)
{
assert(psl);
assert(pos >= 0 && pos < psl->size);
// 挪动覆盖
int begin = pos + 1;
while (begin < psl->size)
{
psl->a[begin - 1] = psl->a[begin];
++begin;
}
psl->size--;
}
Test.c:
#include<stdio.h>
#include"SeqList.h"
void TestSL1()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPushBack(&sl, 6);
SLPushBack(&sl, 7);
SLPushBack(&sl, 8);
SLPushBack(&sl, 9);
SLPrint(&sl);
SLPushFront(&sl, 10);
SLPushFront(&sl, 20);
SLPushFront(&sl, 30);
SLPushFront(&sl, 40);
SLPrint(&sl);
SLDestroy(&sl);
}
void TestSL2()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLPopBack(&sl);
SLPrint(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
SLPrint(&sl);
SLPushFront(&sl, 10);
SLPushFront(&sl, 20);
SLPushFront(&sl, 30);
SLPushFront(&sl, 40);
SLPrint(&sl);
SLDestroy(&sl);
}
void TestSL3()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
//SLPopFront(&sl);
//SLPrint(&sl);
}
void TestSL4()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLInsert(&sl, 2, 20);
SLPrint(&sl);
SLInsert(&sl, 6, 20);
SLPrint(&sl);
SLInsert(&sl, 0, 20);
SLPrint(&sl);
SLInsert(&sl, 10, 20);
SLPrint(&sl);
SLDestroy(&sl);
}
void TestSL5()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLErase(&sl, 3);
SLPrint(&sl);
SLErase(&sl, 3);
SLPrint(&sl);
//SLErase(&sl, 3);
//SLPrint(&sl);
}
int main()
{
TestSL5();
return 0;
}
单链表:
SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLNDataType;
// Single List
typedef struct SListNode
{
SLNDataType val;
struct SListNode* next;
}SLNode;
void SLTPrint(SLNode* phead);
void SLTPushBack(SLNode** pphead, SLNDataType x);
void SLTPushFront(SLNode** pphead, SLNDataType x);
void SLTPopBack(SLNode** pphead);
void SLTPopFront(SLNode** pphead);
SLNode* SLTFind(SLNode* phead, SLNDataType x);
// posǰ
SLNode* SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);
// ɾposλ
void SLTErase(SLNode** pphead, SLNode* pos);
void SLTDestroy(SLNode** pphead);
SList.c
#include"SList.h"
void SLTPrint(SLNode* phead)
{
SLNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->val);
cur = cur->next;
}
printf("NULL\n");
}
SLNode* CreateNode(SLNDataType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLNode** pphead, SLNDataType x)
{
SLNode* newnode = CreateNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
// 找尾
SLNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SLTPushFront(SLNode** pphead, SLNDataType x)
{
SLNode* newnode = CreateNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopBack(SLNode** pphead)
{
// 温柔的检查
//if (*pphead == NULL)
// return;
// 空
assert(*pphead);
// 1、一个节点
// 2、一个以上节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
// 找尾
/*SLNode* prev = NULL;
SLNode* tail = *pphead;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;*/
SLNode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
void SLTPopFront(SLNode** pphead)
{
assert(*pphead);
SLNode* tmp = *pphead;
free(tmp);
*pphead = (*pphead)->next;
}
Test.c文章来源:https://www.toymoban.com/news/detail-833483.html
#include"SList.h"
void TestSLT1()
{
SLNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
//SLTPopBack(&plist);
//SLTPrint(plist);
}
void TestSLT2()
{
SLNode* plist = NULL;
SLTPushFront(&plist, 1);
SLTPushFront(&plist, 2);
SLTPushFront(&plist, 3);
SLTPushFront(&plist, 4);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
//SLNode* pos = SLTFind(plist, 3);
//SLTInsert(&plist, pos, 30);
}
//int main()
//{
// TestSLT2();
//
// return 0;
//}
struct ListNode
{
struct ListNode* next;
int val;
};
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* prev = NULL;
struct ListNode* cur = head;
//while(cur != NULL)
while (cur)
{
if (cur->val == val)
{
struct ListNode* next = cur->next;
free(cur);
if (prev)
prev->next = next;
cur = next;
}
else
{
prev = cur;
cur = cur->next;
}
}
return head;
}
int main()
{
struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));
n1->val = 7;
n2->val = 7;
n3->val = 7;
n4->val = 7;
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = NULL;
struct ListNode* head = removeElements(n1, 7);
return 0;
}
文章来源地址https://www.toymoban.com/news/detail-833483.html
到了这里,关于【数据结构与算法】顺序表与链表(单链表和双链表)超详解图示与源码。的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!