目录
1.线性表
2.顺序表实现
2.1概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存
储。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储元素。
- 动态顺序表:使用动态开辟的数组存储。
// 顺序表的静态存储
#define MAX_SIZE 100
typedef int SQDataType;
typedef struct SeqList
{
SQDataType a[N]; // 定长数组
int size; // 有效数据的个数
}SL;
// 顺序表的动态存储
typedef struct SQDataType
{
SQDataType * a; // 指向动态开辟的数组
int size ; // 有效数据个数
int capicity ; // 容量空间的大小
}SL;
1. 常量使用 define 定义 ,类型使用 typedef 定义,使用define 和 typedef 可以增强程序的维护性。
3. 使用typedef 重定义结构体类型 ,使后面的使用更加简洁。
4. 顺序表的实现使用动态存储,因为静态存储有问题:给少了不够用,给多了用不完浪费,不能灵活控制。
5. 多个变量可以定义结构体。
2.2 接口实现:
静态表
//增删查改等的接口函数
#include"SeqList.h"
void SeqListInit(SL* ps)
{
memset(ps->a, 0, sizeof(SQDataType) * MAX_SIZE); // 将开辟的数组全部初始化为0
ps->size = 0; // 开始里面没有存储一个数据所以为0
}
// 头插 尾插 头删 尾删
void SeqListPushBack(SL* ps, SQDataType x)
{
if (ps->size >= MAX_SIZE) // 每当给顺序表里添加数据时都要判断是否满了
{
printf("SeqList is full!\n");
return;
}
ps->a[ps->size] = x;
ps->size++;
}
void SeqListPushFront(SL* ps, SQDataType x); // 头插
void SeqListPopBack(SL* ps); // 尾删
void SeqListPopFront(SL* ps); // 头删
。。。。。。
。。。。。。
动态表
1. 接口头文件(SeqList.h)
#pragma once // 防止头文件被重复的包含
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
//增强了程序的维护性
typedef int SQDataType;
///*动态的*/
typedef struct SeqList //多个变量可以定义结构体
{
SQDataType* a; // 指向动态开辟的数组
int size; //有效数据的容量
int capacity; //容量空间的大小
}SL;
// 基本增删查改的接口
/*1.连续的物理空间存储--数组*/
/*2.数据必须是从开头开始,依次存储 */
// 顺序表的初始化
void SeqListInit(SL*ps);
// 顺序表的销毁
void SeqListDestory(SL*ps);
// 顺序表的打印
void SeqListPrint(SL*ps);
// 检查空间,如果满了,进行增容
void CheckCapacity(SL*ps);
// 顺序表尾插
void SeqListPushBack(SL*ps, SQDataType x);
// 顺序表尾删
void SeqListPopBack(SL*ps, SQDataType x);
// 顺序表头插
void SeqListPushFront(SL*ps, SQDataType x);
// 顺序表头删
void SeqListPopFront(SL*ps);
// 顺序表查找
int SeqListFind(SL*ps, SQDataType x);
// 顺序表在pos位置修改
void SeqListModity(SL* ps, int pos, SQDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SL*ps, int pos, SQDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SL*ps, int pos);
2. 接口函数(SeqList.c)
#include"SeqList.h"
// 顺序表的初始化
void SeqListInit(SL* ps)
{
ps->a = NULL; // 开始也可以给一些空间
ps->size = 0;
ps->capacity = 0;
}
// 顺序表的销毁
void SeqListDistroy(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
// 顺序表的打印
void SeqListPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
// 检查空间,如果满了,进行增容
void SeqListCheakCapacity(SL* ps)
{
// 满了就要增容
if (ps->size == ps->capacity)
{
//ps->capacity *= 2; // 注意这里容易犯错误,0*任何都为0
//所以需要这句
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
// 用realloc //这里写错的检查半天 用 newcapacity,ps->capacity=0;
SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * 2 * sizeof(SQDataType));
//开辟空间失败,退出程序
if (tmp == NULL)
{
printf("realloc fail!\n");
exit(-1);
}
else //开辟成功
{
ps->a = tmp;
//ps->capacity *= 2; // 注意这里容易犯错误,0*任何都为0
ps->capacity = newcapacity; // 这里的size是不变的
}
}
}
// 顺序表尾插
void SeqListPushBack(SL*ps, SQDataType x)
{
SeqListCheakCapacity(ps); // 插入前要先检查
ps->a[ps->size] = x;
ps->size++;
}
// 顺序表尾删
void SeqListPopBack(SL*ps, SQDataType x)
{
assert(ps->size > 0); // 大于零继续,否则报错
//ps->a[ps->size-1]=0 不需要
ps->size--;
}
// 顺序表头插
void SeqListPushFront(SL*ps, SQDataType x)
{
SeqListCheakCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
// 顺序表头删
void SeqListPopFront(SL*ps)
{
assert(ps->size > 0);
int start = 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
ps->size--;
}
// 顺序表查找
int SeqListFind(SL*ps, SQDataType x)
{
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return 1;
}
}
return -1;
}
// 顺序表在pos位置修改
void SeqListModity(SL* ps, int pos, SQDataType x)
{
assert(pos < ps->size);
ps->a[pos] = x;
}
// 顺序表在pos位置插入x
void SeqListInsert(SL*ps, int pos, SQDataType x)
{
assert(pos < ps->size);
SeqListCheakCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[pos] = x;
ps->size++;
}
// 顺序表删除pos位置的值
void SeqListErase(SL*ps, int pos)
{
assert(pos < ps->size);
int start = pos + 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
ps->size--;
}
3. 主文件(text.c)
可以将其写成一个通讯录,调用顺序表的各个功能
#include "SeqList.h"
///注意里面要传sl的地址
void TestSeqList1()
{
SL sl;
SeqListInit(&sl);
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 2);
SeqListPushBack(&sl, 3);
SeqListPushBack(&sl, 4);
SeqListPushBack(&sl, 5);
SeqListPushBack(&sl, 6);
SeqListPushBack(&sl, 7);
SeqListPushBack(&sl, 8);
SeqListPushBack(&sl, 9);
SeqListPushBack(&sl, 10);
SeqListPrint(&sl);
SeqListDestory(&sl);
}
void TestSeqList2()
{
SL sl;
SeqListInit(&sl);
SeqListPushFront(&sl, 1);
SeqListPushFront(&sl, 2);
SeqListPushFront(&sl, 3);
SeqListPushFront(&sl, 4);
SeqListPushFront(&sl, 5);
SeqListPushFront(&sl, 6);
SeqListPrint(&sl);
SeqListPopBack(&sl);
SeqListPopBack(&sl);
SeqListPopBack(&sl);
SeqListPrint(&sl);
SeqListPopFront(&sl);
SeqListPrint(&sl);
SeqListDestory(&sl);
}
void TestSeqList3()
{
SL sl;
SeqListInit(&sl);
SeqListPushFront(&sl, 1);
SeqListPushFront(&sl, 2);
SeqListPushFront(&sl, 3);
SeqListPushFront(&sl, 4);
SeqListPushFront(&sl, 5);
SeqListPushFront(&sl, 6);
SeqListPrint(&sl);
SeqListInsert(&sl, 1, 20);
SeqListPrint(&sl);
SeqListErase(&sl, 1);
SeqListPrint(&sl);
SeqListDestory(&sl);
}
void menu()
{
printf("**********************************************\n");
printf("1.尾插数据, 2.头插数据\n");
printf("3.尾删数据, 4.头删数据\n");
printf("5.打印数据,-1.退出\n");
printf("**********************************************\n");
printf("请输入你操作选项:");
}
int main()
{
SL s;
SeqListInit(&s);
int option = 0;
int x = 0;
while (option != -1)
{
menu();
scanf("%d", &option);
switch (option)
{
case 1:
printf("请输入你要插入的数据,以-1结束\n");
do {
scanf("%d", &x);
if (x != -1)
{
SeqListPushBack(&s, x);
}
} while (x != -1);
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
SeqListPrint(&s);
break;
default:
break;
}
}
SeqListDestory(&s);
return 0;
}
顺序表的问题及思考
题:
- 中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到
200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?下面给出了链表的结构来看看。
3. 链表
链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。
链表的分类
-
单向或者双向
-
带头或者不带头
-
循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。 - 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带
来很多优势,实现反而简单了,后面我们代码实现了就知道了。
单链表的实现
SList.h
// 1、无头+单向+非循环链表增删查改实现
#pragma once
#include <stdio.h>
#include <stdlib.h>
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data; // 存放节点的数据
struct SListNode* next; // 下一个节点的位置
};
typedef struct SListNode SLTNode;
// 动态申请一个节点
SLTNode* BuySListNode(SLTDataType x);
/*(不会改变链表的头指针,传一级指针)*/
// 单链表打印
void SListPrint(SLTNode* phead);
/*(可能会改变链表的头指针,传二级指针)*/
// 单链表尾插
void SListPushBack(SLTNode** pphead, SLTDataType x);
// 单链表的头插
void SListPushFront(SLTNode** pphead, SLTDataType x);
// 单链表的尾删
void SListPopBack(SLTNode** pphead);
// 单链表头删
void SListPopFront(SLTNode** pphead);
// 单链表查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x);
// 单链表在pos位置之前插入x
void SListInsert(SLTNode** phead, SLTNode* pos, SLTDataType x);
// 单链表删除pos位置的值
void SListErase(SLTNode** phead, SLTNode* pos);
// 有些地方也有这样的
在pos的前面插入x
//void SListInsert(SLTNode** phead, int i, SLTDataType x);
删除pos位置的值
//void SListErase(SLTNode** phead, int i);
SList.c
#include "SList.h"
// 动态申请一个节点
SLTNode* BuySListNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
//当引发了异常: 写入访问权限冲突,data无法读取内存
//错误是没有引用包含 malloc 的头文件
newnode->data = x;
newnode->next = NULL;
return newnode;
}
/*(不会改变链表的头指针,传一级指针)*/
// 单链表打印
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL) // 可以理解为看自己为不为空
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
/*(可能会改变链表的头指针,传二级指针)*/
// 单链表尾插
// pphead 存放的是 plist的地址,要改变plist,需要解引用(*pphead)
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
// 找尾节点的指针
SLTNode* tail = *pphead;
while (tail->next != NULL) // 可以理解为看下一个为不为空
{
tail = tail->next;
}
// 尾节点,链接新节点
tail->next = newnode;
}
}
// 单链表的头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
// 单链表的尾删
void SListPopBack(SLTNode** pphead)
{
// 1. 空
// 2. 一个节点
// 3. 一个以上节点
if (*pphead == NULL)
{
return;
}
else if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* pre = NULL;
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
pre = tail;
tail = tail->next;
}
pre->next = NULL;
}
}
// 单链表头删
void SListPopFront(SLTNode** pphead)
{
// 先保存,后free
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
// 单链表查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
// 单链表在pos位置之前插入x
void SListInsert(SLTNode** phead, SLTNode* pos, SLTDataType x)
{
// 当只有一个节点
if (pos == *pphead)
{
SListPushFront(pphead, x);
}
else
{
SLTNode* newnode = BuySListNode(x);
SLTNode* pre = *pphead;
while (pre->next != pos)
{
pre = pre->next;
}
pre->next = newnode;
newnode->next = pos;
}
}
// 单链表删除pos位置的值
void SListErase(SLTNode** phead, SLTNode* pos)
{
if (pos == *pphead)
{
SListPopFront(pphead, pos);
}
else
{
SLTNode* pre = *pphead;
while (pre->next != pos)
{
pre = pre->next;
}
pre->next = pos->next;
free(pos);
}
}
text.c
#include "SList.h"
void TestSList1()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1); // 这里要传地址,因为要改变plist
SListPushBack(&plist, 2); // plist又是一个指针,所以后面函数要用二级指针
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushFront(&plist, 0);
SListPrint(plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPrint(plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPrint(plist);
}
void TestSList2()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPrint(plist);
SListPopBack(&plist);
SListPopBack(&plist);
SListPopBack(&plist);
SListPopBack(&plist);
SListPopBack(&plist);
SListPrint(plist);
}
void TestSList3()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPrint(plist);
// 想在3的前面插入一个30 ,需要先找到要删除节点的位置pos
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);
}
void TestSList4()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPrint(plist);
SLTNode* pos = SListFind(plist, 1);
if (pos)
{
SListErase(&plist, pos);
}
SListPrint(plist);
pos = SListFind(plist, 4);
if (pos)
{
SListErase(&plist, pos);
}
SListPrint(plist);
pos = SListFind(plist, 3);
if (pos)
{
SListErase(&plist, pos);
}
SListPrint(plist);
pos = SListFind(plist, 2);
if (pos)
{
SListErase(&plist, pos);
}
SListPrint(plist);
}
int main()
{
TestSList4();
return 0;
}
双链表的实现
List.h
// 2、带头+双向+循环链表增删查改实现
// 带头双向循环 -- 最优链表结构,任意位置插入删除都是O(1)
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}ListNode;
// 创建节点
ListNode* BuyListNode(LTDataType x)
// 双向链表初始化
ListNode* ListInit();
// 双向链表销毁
void ListDestory(ListNode* phead);
// 双向链表打印
void ListPrint(ListNode* phead);
// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* phead);
// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* phead);
// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
//bool ListEmpty(ListNode* phead);
//int ListSize(ListNode* phead);
List.c
// 创建节点
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
// 双向链表初始化
ListNode* ListInit()
{
ListNode* phead = BuyListNode(0);
phead->next = phead; // 哨兵节点指向自己
phead->prev = phead; // 哨兵节点指向自己
return phead;
}
// 双向链表销毁
void ListDestory(ListNode* phead)
{
assert(phead); // 断言传入的phead是否为空
ListNode* cur = phead->next;
while (cur != phead) // 双向循环链表有一个哨兵节点
{
// 要先记录下一个节点,然后释放当前节点,否者会找不到下一个节点
ListNode* next = cur->next;
free(cur);
cur = next;
}
// 释放哨兵节点
free(phead);
phead = NULL;
}
// 双向链表打印
void ListPrint(ListNode* phead)
{
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
/// 断言的好处,当代码有几十万行代码时,这里phead传入错误时
/// 代码运行后里面会显示某个.c某行出现问题了
assert(phead);
//ListNode* tail = phead->prev; //找尾节点
//ListNode* newnode = BuyListNode(x); //创建新的节点
//tail->next = newnode; //尾节点指向新的节点
//newnode->prev = tail; //新的节点要指向前面的节点
//newnode->next = phead; //新创建的尾节点要指向第一个节点
//phead->prev = newnode; //第一个节点要重新指向新创建的最后的节点
ListInsert(phead, x); /// 尾插就是在phead前面
}
// 双向链表尾删
void ListPopBack(ListNode* phead)
{
assert(phead);
//assert(phead->next != phead); // 当没有节点时,不能删
//ListNode* tail = phead->prev;
//ListNode* prev = tail->prev;
//prev->next = phead;
//phead->prev = prev;
//free(tail);
//tail = NULL;
ListErace(phead->prev);
}
// 双向链表头插(不推荐写法,顺序不能变不易掌控)
//void ListPushFront(ListNode* phead, LTDataType x)
//{
// assert(phead);
//
// //ListNode* first = phead->next;
// ///在不创建first,后面的顺序就不能变
// ///这种方法不推荐
// ListNode* newnode = BuyListNode(x);
//
// newnode->next = phead->next;
// phead->next->prev = newnode;
//
// phead->next = newnode;
// newnode->prev = phead;
//}
// 双向链表头插(推荐写法)
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
//ListNode* first = phead->next;
//ListNode* newnode = BuyListNode(x);
//phead->next = newnode;
//newnode->next = first;
//first->prev = newnode;
//newnode->prev = phead;
ListInsert(phead->next, x);
}
// 双向链表头删
void ListPopFront(ListNode* phead)
{
assert(phead);
//assert(phead->next!=phead); // 当没有节点时,不能删
//ListNode* first = phead->next;
//ListNode* second = first->next;
//phead->next = second;
//second->prev = phead;
//free(first);
//first = NULL;
ListErace(phead->next);
}
// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
// prev newnode pos
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
//bool ListEmpty(ListNode* phead);
//int ListSize(ListNode* phead);
text.c
#include "List.h"
void TestList1()
{
ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);
ListPushFront(plist, 0);
ListPushFront(plist, -1);
ListPrint(plist);
ListPopFront(plist);
ListPopFront(plist);
ListPopFront(plist);
ListPrint(plist);
ListPopBack(plist);
ListPrint(plist);
ListDestory(plist);
}
void TestList2()
{
ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);
ListNode* pos = ListFind(plist, 3);
if (pos)
{
// 查找,附带着修改的作用
pos->data *= 10;
printf("找到了,并且节点的值乘以10\n");
}
else
{
printf("没有找到\n");
}
ListPrint(plist);
ListInsert(pos, 300);
ListPrint(plist);
ListErase(pos);
ListPrint(plist);
}
int main()
{
TestList1();
return 0;
}