一、线性表
-
概念:n个具有相同特性的数据元素的有限序列。
-
特点:逻辑上是线性的,但物理结构上不一定是连续的。
- 分类:物理存储时,通常以数组和链式结构的形式存储。
二、顺序表
一段物理地址连续的存储单元,依次存储数据元素的线性结构
只能从头部开始 连续存储
-
静态顺序表:使用定长数组存储元素
-
动态顺序表:使用动态开辟的数组存储
typedef int SLDataType;//类型重定义
typedef struct SeqList//类型重定义
{
SLDataType* a;
int size; //有效数据
int caoacity; //空间容量
}SL;
// SeqList.c
#include "SeqList.h"
void SeqListInit(SeqList* ps)
{
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
void SeqListDestroy(SeqList* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
void SeqListPrint(SeqList* ps)
{
assert(ps);
for (inti = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("%\n");
}
void CheckCacpity(SeqList* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
ps->a = (SLDateType*)realloc(ps->a, newcapacity*sizeof(SLDateType));
ps->capacity = newcapacity;
}
}
// 以下几个接口先讲不复用Insert和Erase的实现,最后再讲复用实现
void SeqListPushBack(SeqList* ps, SLDateType x)
{
//assert(ps);
//CheckCacpity(ps);
//ps->a[ps->size] = x;
//ps->size++;
SeqListInsert(ps, ps->size, x);
}
void SeqListPushFront(SeqList* ps, SLDateType x)
{
assert(ps);
/*CheckCacpity(ps);
int end = ps->size;
while (end > 0)
{
ps->a[end] = ps->a[end - 1];
--end;
}
ps->a[0] = x;
++ps->size;*/
SeqListInsert(ps, 0, x);
}
void SeqListPopFront(SeqList* ps)
{
assert(ps);
//int start = 0;
//while (start < ps->size-1)
//{
// ps->a[start] = ps->a[start + 1];
// ++start;
//}
//int start = 1;
//while (start < ps->size)
//{
// ps->a[start-1] = ps->a[start];
// ++start;
//}
//--ps->size;
SeqListErase(ps, 0);
}
void SeqListPopBack(SeqList* ps)
{
assert(ps);
//ps->a[ps->size - 1] = 0;
//ps->size--;
SeqListErase(ps, ps->size-1);
}
int SeqListFind(SeqList* ps, SLDateType x)
{
for (int i = 0; i < ps->size; ++i)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDateType x)
{
assert(ps);
assert(pos <= ps->size);
CheckCacpity(ps);
//int end = ps->size - 1;
//while (end >= pos)
//{
// ps->a[end + 1] = ps->a[end];
// --end;
//}
int end = ps->size ;
while (end > pos)
{
ps->a[end] = ps->a[end - 1];
--end;
}
ps->a[pos] = x;
ps->size++;
}
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos)
{
assert(ps && pos < ps->size);
//int start = pos;
//while (start < ps->size-1)
//{
// ps->a[start] = ps->a[start + 1];
// ++start;
//}
int start = pos+1;
while (start < ps->size)
{
ps->a[start-1] = ps->a[start];
++start;
}
ps->size--;
}
三、链表
1、无头(无哨兵位)单向不循环链表----结构最简单的链表
#include"SList.h"
}
else//多个指针
{
SLNode* tail = *pphead;
SLNode* prev = NULL;//尾的前一个
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;//不置空也可以 但是养成良好习惯
prev->next = NULL;
}
}
//头删
void SLTPopFront(SLNode** pphead)
{
assert(*pphead);
//一个或多个结点都能处理
SLNode* tem = (*pphead)->next;
free(*pphead);//free *pphead所指向的结点
*pphead = tem;
}
//查找
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{
while (phead->val != x)
{
if (phead->next == NULL)
{
printf("链表中没有想要的值。");
}
else
{
phead = phead->next;
}
}
return phead;
}
//在pos前插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{
assert(pphead);
assert((pos && *pphead)|| (!pos && !(*pphead)));
SLNode* newnode = CreateNode(x);
if (pos != *pphead)//pos不是第一个结点
{
while ((*pphead)->next != pos)//找pos之前一个结点
{
*pphead = (*pphead)->next;
}
newnode->next = (*pphead)->next;
(*pphead)->next = newnode;
}
else
{
SLNode* newnode = CreateNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
}
//删除pos位置
void SLTEares(SLNode** pphead, SLNode* pos)
{
if (pos != *pphead)//pos不是第一个结点
{
while ((*pphead)->next != pos)//找pos之前一个结点
{
*pphead = (*pphead)->next;
}
(*pphead)->next = pos->next;
free(pos);
pos = NULL;
}
else
{
*pphead = pos->next;
free(pos);
pos = NULL;
}
}
void SLTDestory(SLNode** pphead)
{
assert(pphead);
SLNode* cur = *pphead;
while (cur)
{
SLNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
2、带头(有哨兵位)双向循环链表----结构最复杂的链表
#include"List.h"
// 双向链表打印
void ListPrint(LTNode* phead)
{
assert(phead);
printf("哨兵位");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
}
//创建返回链表的头结点
LTNode* ListInit()
{
LTNode* phead = CreateLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
//创造新结点
LTNode* CreateLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
//尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = CreateLTNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
//尾删
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;
}
//头插
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = CreateLTNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
// 头删
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);//判空
LTNode* cur = phead->next;
phead->next = cur->next;
cur->next->prev = phead;
free(cur);
cur = NULL;
}
// 查找
LTNode* ListFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->val != x)
{
cur = cur->next;
}
else
{
return cur;
}
}
return NULL;
}
//在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = CreateLTNode(x);
LTNode* pprev = pos->prev;
newnode->next = pos;
newnode->prev = pprev;
pprev->next = newnode;
pos->prev = newnode;
}
//删除pos位置的节点
void ListErase(LTNode* pos)
{
LTNode* pprev = pos->prev;
LTNode* pnext = pos->next;
pprev->next = pnext;
pnext->prev = pprev;
free(pos);
pos = NULL;
}
// 销毁
void ListDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = cur->next;
}
free(phead);
}
四 、顺序表和链表的选择
1、链表(双向为例)优缺点
优点:
- 任意位置插入删除都是O(1)
- 按需申请和释放空间,合理利用空间,不存在浪费
缺点:
- 下标随机访问不方便O(n)
2、顺虚表优缺点
优点:
- 支持下标的随机访问O(n)物理地址连续
缺点:
- 头部或中间插入删除时效率低,需要挪动数据O(n)
- 空间不够需要扩容,有消耗,存在一定的空间浪费
- 只适合尾插