一.顺序表
1.概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表可分为:
1. 静态顺序表:使用定长数组存储元素。
2. 动态顺序表:使用动态开辟的数组存储。
2.动态顺序表的实现
SeqList.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
typedef int SLDateType;
//动态顺序表
typedef struct SeqList
{
SLDateType* a;
int size; // 存储数据个数
int capacity;// 存储空间大小
}SL,SeqList;
//打印出数据
void SeqListPrint(SeqList* psl);
//初始化顺序表
void SeqListInit(SeqList* psl);
//销毁顺序表
void SeqListDestroy(SeqList* psl);
//检查容量
void SeqListCheckcapacity(SeqList* psl);
//在pos位置插入数据x
void SeqListInsert(SeqList* psl, size_t pos, SLDateType x);
//删除pos位置的数据
void SeqListErase(SeqList* psl, size_t pos);
//头插和头删 时间复杂度为 O(N)
void SeqListPopFront(SeqList* psl);//头删数据
void SeqListPushFront(SeqList* psl, SLDateType x);//头插数据
//尾插和尾删 时间复杂度为 O(1)
void SeqListPopBack(SeqList* psl);//尾删数据
void SeqListPushBack(SeqList* psl, SLDateType x);//尾插数据
//顺序表查找
int SeqListFind(SeqList* psl,SLDateType x);
SeqList.c
#include "SeqList.h"
void SeqListPrint(SeqList* psl)
{
assert(psl);
for (int i = 0; i < psl->size; ++i)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
void SeqListInit(SeqList* psl)
{
assert(psl);
psl->a = NULL;
psl->size = 0;
psl->capacity = 0;
}
void SeqListDestroy(SeqList* psl)
{
assert(psl);
free(psl->a);
psl->a = NULL;
psl->size = 0;
psl->capacity = 0;
}
void SeqListCheckcapacity(SeqList* psl)
{
assert(psl);
//如果满了,要扩容
if (psl->size == psl->capacity)
{
size_t newcapacity = 0 ? 4 : psl->capacity * 2;
SLDateType* tmp = (SLDateType*)realloc(psl->a, newcapacity * sizeof(SLDateType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
{
psl->a = tmp;
psl->capacity = newcapacity;
}
}
}
void SeqListPushBack(SeqList* psl,SLDateType x)
{
assert(psl);
//SeqListCheckcapacity(psl);
//psl->a[psl->size] = x;
//psl->size++;
SeqListInsert(psl, psl->size, x);
}
void SeqListPushFront(SeqList* psl, SLDateType x)
{
assert(psl);
/*SeqListCheckcapacity(psl);
int end = psl->size - 1;
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[0] = x;
psl->size++;*/
SeqListInsert(psl, 0, x);
}
void SeqListInsert(SeqList* psl, size_t pos, SLDateType x)
{
assert(psl);
//温和检查
if (pos > psl->size )
{
printf("pos 越界: %d\n", pos);
return;
//exit(-1)
//return 和 exit(-1)的区别:
//return是终止函数 , exit(-1)是直接结束程序
}
//暴力检查
//assert(pos <= psl->size);
SeqListCheckcapacity(psl);
//int end = psl->size - 1;
//
//
// 这里不强制转换(int)的话,end会算术提升到pos的 unsigned int类型
// 那么end将不会出现负数,可能循环会永远进行
//while (end >= (int)pos)
//{
// psl->a[end + 1] = psl->a[end];
// --end;
//}
size_t end = psl->size;
while (end > pos)
{
psl->a[end] = psl->a[end - 1];
--end;
}
psl->a[pos] = x;
psl->size++;
}
void SeqListErase(SeqList* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);
size_t begin = pos + 1;
while (begin < psl->size)
{
psl->a[begin - 1] = psl->a[begin];
++begin;
}
--psl->size;
}
void SeqListPopFront(SeqList* psl)
{
assert(psl);
//if (psl->size > 0)
//{
// int begin = 0;
// while (begin < psl->size - 1)
// {
// psl->a[begin] = psl->a[begin + 1];
// begin++;
// }
// --psl->size;
//}
SeqListErase(psl, 0);
}
void SeqListPopBack(SeqList* psl)
{
assert(psl);
//if (psl->size > 0)
//{
// --psl->size;
//}
SeqListErase(psl, psl->size - 1);
}
int SeqListFind(SeqList* psl, SLDateType x)
{
assert(psl);
int i = 0;
for (i = 0; i < psl->size; i++)
{
if (psl->a[i] == x)
{
return i;
}
}
return -1;
}
需要注意的是:
1.越界不一定能检查出来,越界是抽查,需要我们自己去检查
2.使用free函数的时候,如果在最后free这报错,其他没错误,可能是要释放的空间越界了
二、单链表
1.概念
链表是一种
物理存储结构上非连续
、非顺序的存储结构,数据元素的
逻辑顺序
是通过链表中的
指针链
接
次序实现的 。
?2.单链表的实现
SList.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
typedef int SLDateType;
typedef struct SListNode
{
SLDateType date;
struct SListNode* next;
}SListNode, SL;
//打印数据
void SListPrint(SListNode* phead);
//尾插
void SListPushBack(SListNode** phead, SLDateType x);
//头插
void SListPushFront(SListNode** phead, SLDateType x);
//尾删
void SListPopBack(SListNode** phead);
//头删
void SListPopFront(SListNode** phead);
//查找
SListNode* SListFind(SListNode* phead,SLDateType x);
//在pos位置之前插入数据
void SListInsert(SListNode** phead, SListNode* pos,SLDateType x);
//在pos位置之后插入数据
void SListInsertAfter(SListNode* pos, SLDateType x);
//删除pos位置
void SListErase(SListNode** phead, SListNode* pos);
//删除pos位置之后
void SListEraseAfter( SListNode* pos);
//销毁
void SListDestroy(SListNode** phead);
SList.c
#include "SList.h"
void SListPrint(SListNode* phead)
{
SListNode* cur = phead;
while (cur != NULL)
{
printf("%d\n", cur->date);
cur = cur->next;
}
printf("NULL\n");
}
SListNode* BuySListNode(SLDateType x)
{
SListNode* newnode = (SLDateType*)malloc(sizeof(SLDateType));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
else
{
newnode->date = x;
newnode->next = NULL;
}
return newnode;
}
void SListPushBack(SListNode** phead, SLDateType x)
{
assert(phead);
SListNode* newnode = BuySListNode(x);
if (*phead == NULL)
{
*phead = newnode;
}
else
{
SListNode* tail = *phead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
// 错误写法,这里没有链接起来
//SListNode* tail = *pphead;
//while (tail != NULL)
//{
// tail = tail->next;
//}
//tail = newnode; tail为NULL,上一个tail->next还是NULL
}
}
void SListPushFront(SListNode** phead, SLDateType x)
{
assert(phead);
//不用检查链表头,是否为空,为空照样插入
SListNode* newnode = BuySListNode(x);
newnode->next = *phead;
*phead = newnode;
}
void SListPopBack(SListNode** phead)
{
assert(phead);
//phead 可能为空,*phead也可能为空,要考虑两种情况;
//该链表为空链表时,*phead = Slist = NULL
//可以暴力检查为空的情况
//assert(*phead != NULL)
//链表有三种情况
//1.空链表,无节点
//2.一个节点
//3.多个节点
if (*phead == NULL)//温柔检查
{
return;
}
else if((*phead)->next == NULL)
{
free(*phead);
*phead = NULL;
}
else
{
SListNode* prev = *phead;
SListNode* tail = *phead;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);//将原链尾空间释放
tail = NULL;//将原链尾地址制空
prev->next = NULL;//将链尾的地址制空
}
}
void SListPopFront(SListNode** phead)
{
assert(phead);
if (*phead == NULL)
{
return;
}
else
{
SListNode* next = (*phead)->next;
free(*phead);
*phead = next;
}
}
SListNode* SListFind(SListNode* phead,SLDateType x)
{
SListNode* cur = phead;
while (cur != NULL)
{
if(cur->date == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void SListInsert(SListNode** phead, SListNode* pos, SLDateType x)
{
assert(phead);
assert(pos);
if (pos == *phead)
{
SListPushFront(phead, x);
}
else
{
SListNode* prev = *phead;
while (prev->next != pos)
{
prev = prev->next;
}
SListNode* newnode = BuySListNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
void SListInsertAfter(SListNode* pos, SLDateType x)
{
assert(pos);
SListNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SListErase(SListNode** phead, SListNode* pos)
{
assert(phead);
assert(pos);
if (*phead == pos)
{
SListPopFront(*phead);
}
else
{
SListNode* prev = *phead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
void SListEraseAfter( SListNode* pos)
{
assert(pos);
SListNode* next = pos->next;
if (next)
{
pos->next = next->next;
free(next);
next = NULL;
}
}
void SListDestroy(SListNode** phead)
{
assert(phead);
SListNode* cur = *phead;
while (cur)
{
SListNode* next = cur->next;
free(cur);
cur = next;
}
*phead = NULL;
}
三、双向链表
1.双向链表的实现
List.h
#pragma once
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
//链表打印
void ListPrint(LTNode* phead);
//初始化链表
//void ListInit(LTNode** phead);
//改良后:
LTNode* ListInit();
//创建单个结点
LTNode* BuyLTNode(LTDataType x);
//尾插
void PushBack(LTNode* phead, LTDataType x);
//尾删
void PopBack(LTNode* phead);
//头插
void PushFront(LTNode* phead, LTDataType x);
//头删
void PopFront(LTNode* phead);
//在pos位置前面插入数据
void ListInsert(LTNode* pos, LTDataType x);
//删除pos位置
void ListErase(LTNode* pos);
//查找x的位置
LTNode* ListFind(LTNode* phead, LTDataType x);
//销毁
void ListDestroy(LTNode* phead);
List.c
#include "List.h"
LTNode* BuyLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next ;
while (cur != phead)
{
printf("%d", cur->data);
cur = cur->next;
}
printf("\n\n");
}
//void ListInit(LTNode** phead)
//{
// assert(phead);
// *phead = BuyLTNode(0);
// (*phead)->next = *phead;
// (*phead)->prev = *phead;
//}
//改良后:
LTNode* ListInit()
{
LTNode* phead = BuyLTNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
void PushBack(LTNode* phead, LTDataType x)
{
assert(phead);
//LTNode* tail = phead->prev;
//LTNode* newnode = BuyLTNode(x);
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = phead;
//phead->prev = newnode;
ListInsert(phead, x);//因为插入是在pos的前面,所以此处传链头位置,即插入数据在链尾
}
void PopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
/*LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tail = NULL;
tailPrev->next = phead;
phead->prev = tailPrev;*/
ListErase(phead->prev);
}
void PushFront(LTNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead->next , x);
}
void PopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListErase(phead->next);
}
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = BuyLTNode(x);
LTNode* posPrev = pos->prev;
pos->prev = newnode;
newnode->next = pos;
posPrev->next = newnode;
newnode->prev = posPrev;
}
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
free(pos);
pos = NULL;
posPrev->next = posNext;
posNext->prev = posPrev;
}
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;
}
void ListDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* Next = cur->next;
free(cur);
cur = Next;
}
free(phead);
}
四、顺序表和链表的区别
不同点 | 顺序表 | 链表 |
存储空间上
|
物理上一定连续
|
逻辑上连续,但物理上不一定连续
|
随机访问
|
支持
O(1)
|
不支持:
O(N)
|
任意位置插入或者删除元素
|
可能需要搬移元素,效率低
O(N)
|
只需修改指针指向
|
插入
|
动态顺序表,空间不够时需要扩容
|
没有容量的概念
|
应用场景
|
元素高效存储
+
频繁访问
|
任意位置插入和删除频繁
|
缓存利用率
|
高
|
低
|
顺序表、链表的优缺点
顺序表优点:
1.物理空间是连续的,方便用下标随机访问。
2.CPU高速缓存命中率会更高(了解即可)
顺序表缺点:
1.由于需要物理空间连续,空间不够需要扩容。扩容本身有一定消耗。其次扩容机制还存在一定的空间浪费。
2.头部或者中部删除插入,挪动数据,效率低。O(N)
链表优点:
1.任意位置插入删除效率高。O(1)
2.按需申请和释放空间
链表缺点:
1.不支持下标的随机访问,有些算法不适合在链表上进行。如:二分查找,排序等
总的来说,这两个结构是相辅相成的结构
|