目录
线性表:
顺序表和链表都属于线性表,那么,什么是线性表呢?
线性表(linear list)是n个具有相同特性的数据元素的有限序列。
线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表又分有静态顺序表和动态顺序表。
静态顺序表
#define N 5
typedef int SLDataType;//类型重定义,方便修改为需要的数据类型
typedef struct SeqList
{
SLDataType a[N];
int size;//现存的数据个数
}SL;
动态顺序表
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;// 指向动态开辟的数组
int size; // 现存数据个数
int capacity; // 数组实际能存数据的空间容量
}SL;
接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。
所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小。
下面我们来实现一下动态顺序表。
SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#define N 1000
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
//动态顺序表
typedef struct SeqList
{
SLDataType* a;
int size;
int capacity;//数组实际能存数据的空间容量
}SL;
//接口函数
void SeqListPrint(SL* ps);//顺序表打印
void SeqListInit(SL* ps);//顺序表初始化
void SeqListCheckCapacity(SL* ps);//检查顺序表是否为空
void SeqListPushBack(SL* ps, SLDataType x);//顺序表尾插
void SeqListPopBack(SL* ps);//顺序表尾删
void SeqListPushFront(SL* ps, SLDataType x);//顺序表头插
void SeqListPopFront(SL* ps);//顺序表尾删
void SeqListDestory(SL* ps);//顺序表销毁
//...
int SeqListFind(SL* ps, SLDataType x);//查找到返回位置下标,未找到返回-1
void SeqListInsert(SL* ps, int pos, SLDataType x);//pos下标位置插入
void SeqListErase(SL* ps, int pos);//删除pos位置数据
SeqList.c
#include"SeqList.h"
void SeqListPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->capacity = ps->size = 0;
}
void SeqListCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
void SeqListPushBack(SL* ps, SLDataType x)
{
/*SeqListCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;*/
SeqListInsert(ps, ps->size, x);
}
void SeqListPopBack(SL* ps)
{
//温柔的处理方式
if (ps->size > 0)
{
ps->size--;
}
//粗暴的处理方式
/*assert(ps->size > 0);
ps->size--;*/
}
void SeqListPushFront(SL* ps, SLDataType x)
{
//SeqListCheckCapacity(ps);
///*int end = ps->size - 1;
//while (end >= 0)
//{
// ps->a[end + 1] = ps->a[end];
// end--;
//}*/
//memmove(ps->a + 1, ps->a, ps->size * sizeof(SLDataType));
//ps->a[0] = x;
//ps->size++;
SeqListInsert(ps, 0, x);
}
void SeqListPopFront(SL* ps)
{
//assert(ps->size > 0);
///*int begin = 1;
//while (begin < ps->size)
//{
// ps->a[begin - 1] = ps->a[begin];
// begin++;
//}*/
//memmove(ps->a, ps->a + 1, ps->size * sizeof(SLDataType));
//ps->size--;
SeqListErase(ps, 0);
}
int SeqListFind(SL* ps, SLDataType x)
{
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
assert(pos >= 0 && pos <= ps->size);
SeqListCheckCapacity(ps);
memmove(ps->a + pos + 1, ps->a + pos, (ps->size - pos) * sizeof(SLDataType));
ps->a[pos] = x;
ps->size++;
}
void SeqListErase(SL* ps, int pos)
{
assert(pos >= 0 && pos <= ps->size);
memmove(ps->a + pos, ps->a + pos + 1, (ps->size - 1 - pos) * sizeof(SLDataType));
ps->size--;
}
void SeqListDestory(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
代码测试 test.c
#include"SeqList.h"
void TestSeqList1()
{
SL s1;
SeqListInit(&s1);
SeqListPushBack(&s1, 1);
SeqListPushBack(&s1, 2);
SeqListPushBack(&s1, 3);
SeqListPopBack(&s1);
SeqListPushFront(&s1, 10);
SeqListPushFront(&s1, 20);
SeqListPushFront(&s1, 30);
SeqListPopFront(&s1);
int pos = SeqListFind(&s1, 1);
if (pos != -1)
{
SeqListInsert(&s1, pos, 1);
}
SeqListErase(&s1,pos);
SeqListPrint(&s1);
SeqListDestory(&s1);
}
void Menu()
{
printf("请选择:>\n");
printf("1.头插 2.头删\n");
printf("3.尾插 4.尾删\n");
printf("5.打印 0.退出\n");
printf("****************************\n");
}
void MenuTest()
{
SL s1;
SeqListInit(&s1);
int input, x;
do
{
Menu();
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出\n");
break;
case 1:
printf("请输入你要插入的数据,以-1结束:");
scanf("%d", &x);
while (x != -1)
{
SeqListPushFront(&s1, x);
printf("插入成功,请输入下一个要插入的数据,以-1结束:");
scanf("%d", &x);
}
break;
case 2:
SeqListPopFront(&s1);
printf("删除成功\n");
break;
case 3:
printf("请输入你要插入的数据,以-1结束:");
scanf("%d", &x);
while (x != -1)
{
SeqListPushBack(&s1, x);
printf("插入成功,请输入下一个要插入的数据,以-1结束:");
scanf("%d", &x);
}
break;
case 4:
SeqListPopBack(&s1);
printf("删除成功\n");
break;
case 5:
SeqListPrint(&s1);
break;
default:
printf("无效选择,请重新输入。\n");
break;
}
} while (input);
}
int main()
{
//TestSeqList1();
//MenuTest();
int a;
char ch;
double d;
scanf("%d%c%lf", &a, &ch, &d);//12345 678910.36
printf("%d%c%lf\n", a, ch, d);
return 0;
}
顺序表的问题及思考
优点:
1.支持随机访问
2.CPU高速缓存利用率较链表更高
缺点:
1、空间不够了需要扩容,扩容要付出代价,尤其是异地扩容;
2、避免频繁扩容,一般选择二倍扩容,可能导致一定的空间浪费;
3、顺序表要求从开始位置连续存储,那么在头部或中间位置插入或删除数据就需要挪动数据,效率不高,时间复杂度为 O(N)。
针对顺序表缺陷,下面来看看链表。
链表
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
从上图看,链式结构在逻辑上是连续的,但在物理上不一定连续;
现实中,节点一般都是在堆上申请出来的;
而堆上申请的空间,是按照一定策略分配的,两次申请的空间,可能连续,也可能不连续。
链表的分类
1.单向或者双向
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
单向
双向
2.带头或者不带头
带头
不带头
3.循环或非循环
循环
非循环
虽然链表结构很多,但是实际中最常用的还是下面两种结构:
1. 无头单向非循环链表:
结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2.带头双向循环链表
结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
无头单向非循环链表的实现
SList.h
#pragma once
#define _CRT_SECURE_NO_ARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SListDataType;
typedef struct SListNode
{
SListDataType data;
struct SListNode* next;
}SLTNode;
//头插
void SListPopFront(SLTNode** pphead);
//头删
void SListPushFront(SLTNode** pphead, SListDataType x);
//尾删
void SListPushBack(SLTNode** pphead, SListDataType x);
//尾插
void SListPopBack(SLTNode** pphead);
//打印
void SListPrint(SLTNode* phead);
//查找
SLTNode* SListFind(SLTNode* phead, SListDataType x);
//在pos前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SListDataType x);
//在pos后插入
void SListInsertAfter(SLTNode* pos, SListDataType x);
//void SListInsert(SLTNode* phead, int pos, SListDataType x);
//删除pos节点
void SListErase(SLTNode** pphead, SLTNode* pos);
//删除pos后的节点
void SListEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);
SList.c
#include"SList.h"
SLTNode* BuyListNode(SListDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SListPushFront(SLTNode** pphead, SListDataType x)
{
SLTNode* newnode = BuyListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SListPopFront(SLTNode** pphead)
{
assert(*pphead != NULL);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
void SListPushBack(SLTNode** pphead, SListDataType x)
{
SLTNode* newnode = BuyListNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SListPopBack(SLTNode** pphead)
{
温柔方式
//if (*pphead == NULL)
//{
// return;
//}
//粗暴方式
assert(*pphead != NULL);
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;
}
}
SLTNode* SListFind(SLTNode* phead, SListDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
//在pos后插入,更合适
void SListInsertAfter(SLTNode* pos, SListDataType x)
{
SLTNode* newnode = BuyListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//在pos前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SListDataType x)
{
SLTNode* newnode = BuyListNode(x);
SLTNode* posPrev = *pphead;
if (pos == *pphead)
{
newnode->next = *pphead;
*pphead = newnode;
}
else
{
while (posPrev->next != pos)
{
posPrev = posPrev->next;
}
posPrev->next = newnode;
newnode->next = pos;
}
}
void SListErase(SLTNode** pphead, SLTNode* pos)
{
if (*pphead == pos)
{
*pphead = pos->next;
free(pos);
}
else
{
SLTNode* posPrev = *pphead;
while (posPrev->next != pos)
{
posPrev = posPrev->next;
}
posPrev->next = pos->next;
free(pos);
}
}
void SListEraseAfter(SLTNode* pos)
{
assert(pos->next != NULL);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
}
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void SListDestroy(SLTNode** pphead)
{
SLTNode* cur = *pphead;
while (cur != NULL)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
代码测试
test.c
#include"SList.h"
void TestSList1()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPrint(plist);
SListPopBack(&plist);
SListPopBack(&plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPrint(plist);
}
void TestSList2()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPrint(plist);
//找
SLTNode* pos = SListFind(plist, 2);
int i = 1;
while (pos)
{
printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data);
pos = SListFind(pos->next, 2);
}
SListPrint(plist);
//找+改
pos = SListFind(plist, 3);
while (pos)
{
pos->data = 30;
pos = SListFind(pos->next, 3);
}
SListPrint(plist);
}
void TestSList3()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPrint(plist);
SLTNode* pos = SListFind(plist, 1);
if (pos)
{
SListInsert(&plist, pos, 10);
}
SListPrint(plist);
pos = SListFind(plist, 3);
if (pos)
{
SListInsert(&plist, pos, 30);
}
SListPrint(plist);
SListDestroy(&plist);
}
int main()
{
//TestSList1();
//TestSList2();
TestSList3();
return 0;
}
带头双向循环链表的实现
List.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
LTNode* ListInit();
void ListPushBack(LTNode* phead, LTDataType x);
void ListPopBack(LTNode* phead);
void ListPushFront(LTNode* phead, LTDataType x);
void ListPopFront(LTNode* phead);
LTNode* ListFind(LTNode*phead, LTDataType x);
void ListInsert(LTNode* pos, LTDataType x);
void ListErase(LTNode* pos);
void ListPrint(LTNode* phead);
void ListDestroy(LTNode* phead);
List.c
#include"List.h"
LTNode* ListInit()
{
//哨兵位头节点
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
phead->next = phead;
phead->prev = phead;
return phead;
}
LTNode* BuyListNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
/*LTNode* tail = phead->prev;
LTNode* newnode = BuyListNode(x);
newnode->prev = tail;
newnode->next = phead;
tail->next = newnode;
phead->prev = newnode;*/
ListInsert(phead, x);
}
void ListPopBack(LTNode* phead)
{
//assert(phead);
//assert(phead->next != phead);//防止删掉哨兵位
//
//LTNode* tail = phead->prev;
//LTNode* tailPrev = tail->prev;
//free(tail);
//tailPrev->next = phead;
//phead->prev = tailPrev;
ListErase(phead->prev);
}
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
/*LTNode* newnode = BuyListNode(x);
LTNode* next = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;*/
ListInsert(phead->next, x);
}
void ListPopFront(LTNode* phead)
{
/*assert(phead);
assert(phead->next != phead);
LTNode* next = phead->next->next;
free(phead->next);
phead->next = next;
next->prev = phead;*/
ListErase(phead->next);
}
LTNode* ListFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//pos位置前插入节点
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
newnode->data = x;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
//删除pos
void ListErase(LTNode* pos)
{
assert(pos);
assert(pos->next != pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
free(pos);
prev->next = next;
next->prev = prev;
}
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
void ListDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
代码测试 test.c
#include"List.h"
void TestList1()
{
LTNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPopBack(plist);
ListPushFront(plist, 2);
ListPushFront(plist, 1);
ListPopFront(plist);
ListPrint(plist);
LTNode* pos = ListFind(plist, 2);
if (pos)
{
ListInsert(pos, 1);
}
ListPrint(plist);
pos = ListFind(plist, 1);
if (pos)
{
ListErase(pos, 1);
}
ListPrint(plist);
ListDestroy(plist);
plist = NULL;
}
int main()
{
TestList1();
return 0;
}
顺序表和链表的区别
顺序表 | 链表 | |
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连 续 |
随机访问 | 支持 时间复杂度O(1) | 不支持 时间复杂度O(N) |
任意位置插入或者删除元素 | 可能需要搬移元素,效率低O(N) | 只需修改指针指向 |
插入 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
缓存利用率 | 高 | 低 |