🍺🍺哈喽,大家好丫,你们的小郭子又来啦 ~
话不多说,直接上干货,嘻嘻嘻 ~
目录
1. 顺序表和链表的区别和联系
1.1 顺序表的优点
1.结构简单,尾插尾删时间复杂度为O(1);
2.可以随机访问;
3.缓存命中率较高(相对于链式),物理空间连续,预加载有优势;
1.2 顺序表的缺点
1.中间和头部的插入和删除的时间复杂度为O(N);
2.增容需要增长空间,就会需要拷贝数据,会产生消耗;
3.空间的浪费:扩容一般以两倍的速度扩容,这是因为不会因为扩容太小而频繁扩容,也不至于太大浪费太多空间,但是在整体上来说还是会有一定的空间浪费;
1.3 链表的优点
1.不会造成空间浪费,用一个节点开辟一个节点;
2.双向链表在任意位置的插入删除都是O(1);
1.4 链表的缺点
1.以节点为存储单位,不支持随机访问;
2.非连续结构,结构较为复杂;
3.缓存命中率低,并且容易造成内存碎片;
2 .顺序表
2.1 定义
2.2 顺序表的储存
2.3 顺序表的实现
2.3.1 SeqList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#define ListSize 100 //线性表的最大长度
typedef int DataType;
typedef struct
{
DataType data[ListSize]; //用数组存储线性表中的元素
DataType length; //顺序表的长度
}SeqList, *PSeqList;
void InitList(PSeqList L); //顺序表的初始化操作
int LengthList(PSeqList L); //求顺序表的长度
int GetData(PSeqList L, int i); //返回数据表中第i个元素的值
int InsList(PSeqList L, int i, DataType e); //在顺序表的第i个位置插入元素
int DelList(PSeqList L, DataType i, DataType* e); //删除顺序表L的第i个数据元素
int Locate(PSeqList L, DataType e); //查找数据元素e在表中的位置
void PushFront(PSeqList L, DataType e); //头插,在表头插入元素e
void PopFront(PSeqList L); //头删,删除表中的第一个元素
void PushBack(PSeqList L, DataType e); //尾插,在表尾插入元素e
void PopBack(PSeqList L); //尾删,删除表尾元素
void ClearList(PSeqList L); //清空顺序表
int EmptyList(PSeqList L); //判断顺序表是否为空
void PrintList(PSeqList L); //打印表中元素
2.3.2 main.c
#include "SeqList.h"
int main()
{
SeqList L;
DataType data;
//初始化顺序表
InitList(&L);
//在表中插入元素
printf("依次在表中插入元素(1,2,3,4,5):\n");
InsList(&L, 1, 1);
InsList(&L, 2, 2);
InsList(&L, 3, 3);
InsList(&L, 4, 4);
InsList(&L, 5, 5);
//打印表中元素
printf("表中元素有:\n");
PrintList(&L);
//头插
printf("在表头依次插入元素,6,7:\n");
PushFront(&L, 6);
PushFront(&L, 7);
//尾插
printf("在表尾依次插入元素,8,9:\n");
PushBack(&L, 8);
PushBack(&L, 9);
printf("表中元素有:\n");
PrintList(&L);
//头删
printf("头删一个元素:\n");
PopFront(&L);
//尾删
printf("尾删一个元素:\n");
PopBack(&L);
//输出表中第4个元素值
PrintList(&L);
printf("表中第4个元素值为:\n%d\n",GetData(&L, 4));
//查找表中第 i个元素的位置
printf("元素2在表中的位置为:\n");
printf("%d\n",Locate(&L, 2));
//删除表中第2个元素对应的值
printf("删除表中第2个元素:%d\n", DelList(&L, 2, &data));
printf("顺序表的长度为:%d\n", LengthList(&L));
printf("表中元素为:\n");
PrintList(&L);
//printf("删除的元素值为:%d\n", data);
//清空顺序表
ClearList(&L);
PrintList(&L);
return 0;
}
2.3.3 SeqList.c
#include "SeqList.h"
int k = 0; //全局变量,用于作部分操作的循环变量
//初始化顺序表
void InitList(PSeqList L)
{
if (L == NULL)
{
return;
}
L->length = 0;
}
//求顺序表的长度
int LengthList(PSeqList L)
{
if (L == NULL)
{
return 0;
}
return L->length;
}
//返回数据表中第i个元素的值
int GetData(PSeqList L, int i)
{
if (L->length < 1 || (L->length > LengthList(L)))
{
return 0;
}
//数据元素的序号从1开始,数组下表从0开始,第i个元素对应的数组下标为i-1;
return L->data[i - 1];
}
//在L中第i个位置,插入新的数据元素e
int InsList(PSeqList L, int i, DataType e)
{
//判断插入位置是否合法
if (i < 1 || L->length >(LengthList(L) + 1))
{
printf("插入位置不合法!\n");
return 0;
}
//判断顺序表是否已满
else if (L->length >= ListSize)
{
printf("顺序表已满,不能插入!\n");
return 0;
}
else
{
for (k = i; k <= L->length; k--)
{
L->data[k + 1] = L->data[k];
}
L->data[i - 1] = e;
L->length++; //数据表的长度加1
return 1;
}
return 0;
}
//删除L的第i个数据元素
int DelList(PSeqList L, DataType i, DataType* e)
{
if (L->length < 1)
{
printf("表为空!\n");
return 0;
}
*e = L->data[i - 1];
for (k = i; k < L->length; k++)
{
L->data[k - 1] = L->data[k];
}
L->length--;
return *e;
}
//查找e在表中的位置
int Locate(PSeqList L, DataType e)
{
for (k = 0; k < L->length; k++)
{
if (L->data[k] == e)
{
//k为e对应的数组下标,在表中对应序号应为k+1
return k + 1;
}
}
return 0;
}
//头插,在表头插入元素e
void PushFront(PSeqList L, DataType e)
{
if (L->length == ListSize)
{
printf("顺序表已满,不能插入!\n");
}
//将表中元素依次后移一位
for (k = L->length; k > 0; k--)
{
L->data[k] = L->data[k - 1];
}
//插入元素
L->data[0] = e;
L->length++;
}
//头删,删除顺序表中的第一个元素,把顺序表中的元素依次往前移动一位
void PopFront(PSeqList L)
{
if (EmptyList(L))
{
printf("顺序表为空,不能插入!\n");
}
for (k = 1; k <= L->length - 1; k++)
{
L->data[k - 1] = L->data[k];
}
L->length--;
}
//尾插
void PushBack(PSeqList L, DataType e)
{
if (L->length == ListSize)
{
printf("顺序表已满,不能插入!\n");
}
L->data[L->length] = e;
L->length++;
}
//尾删
void PopBack(PSeqList L)
{
if (EmptyList(L))
{
printf("表为空!\n");
}
L->length--;
}
//清空顺序表
void ClearList(PSeqList L)
{
L->length = 0;
}
//判断表是否为空
int EmptyList(PSeqList L)
{
if (L->length == 0)
{
return 1;
}
return 0;
}
//打印表中元素
void PrintList(PSeqList L)
{
if (EmptyList(L))
{
printf("表为空!\n");
return;
}
for (k = 0; k < L->length; k++)
{
printf("%-3d", L->data[k]);
}
printf("\n");
}
3.链表
3.1 单链表的实现
3.1.1 SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SListDataType;
//结点
typedef struct SListNode {
SListDataType data;//数据域
struct SListNode* next;//指针域
}SListNode;
//尾插
void SListPushBack(SListNode** pphead, SListDataType x);
//尾删
void SListPopBack(SListNode** pphead);
//头插
void SListPushFront(SListNode** pphead, SListDataType x);
//头删
void SListPopFront(SListNode** pphead);
//查找
SListNode* SListFind(SListNode*phead, SListDataType x);
//pos位置后插入
void SListInsertAfter(SListNode*pos, SListDataType x);
//删除pos位置后的值
void SListEraseAfter(SListNode* pos);
//打印
void SListPrint(SListNode* phead);
3.1.2 main.c
#include "SList.h"
void TestSList1() {
//头指针
SListNode* pList = NULL;//pList永远指着第一个
//...
SListPushBack(&pList, 1);
SListPushBack(&pList, 2);
SListPushBack(&pList, 3);
SListPushBack(&pList, 4);
SListPushBack(&pList, 5);
SListPushBack(&pList, 6);
//这里要传地址,所以SList.c那边要用二级指针来接受
SListPrint(pList);
//打印是不需要传地址的,因为不需要改变内容,跟以前C语言学的是一样的道理
SListPopBack(&pList);
SListPopBack(&pList);
SListPrint(pList);
SListPushFront(&pList, 0);
SListPushFront(&pList, -1);
SListPushFront(&pList, -2);
SListPrint(pList);
SListPopFront(&pList);
SListPopFront(&pList);
SListPopFront(&pList);
SListPrint(pList);
}
void TestSList2() {
//头指针
SListNode* pList = NULL;//pList永远指着第一个
//...
SListPushBack(&pList, 1);
SListPushBack(&pList, 2);
SListPushBack(&pList, 3);
SListPushBack(&pList, 4);
SListPushBack(&pList, 5);
SListPushBack(&pList, 6);
SListPrint(pList);
//测试查找,顺便修改一下
SListNode* pos = SListFind(pList, 3);
if (pos) {//如果查找接口返回的不是NULL,则修改
pos->data = 30;
}
SListPrint(pList);
}
int main() {
TestSList1();
TestSList2();
return 0;
}
3.1.3 SList.c
#include "SList.h"
//单链表的遍历
void SListPrint(SListNode* phead)
{
//不用assert
//因为它完全就是有可能为空的
SListNode* cur = phead;
while (cur != NULL)
{
printf("%d-->", cur->data);
cur = cur->next;//这里的next是下一个节点的地址
//这里不能够是cur++;因为链表结点地址不连续
//但是我们的指针域里面存了下一个结点的地址,我们直接
//cur=cur->next;
}
//以前顺序表其实就是数组,我们挨个挨个,用循环就可以打印了
//但是这里不一样,这里是链表,串起来的
//地址不一定连续
printf("NULL\n");//这样可以美化一下
}
//开辟节点接口
SListNode* BuySListNode(SListDataType x) {
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));//开一个结点
if (newNode == NULL) {
printf("申请结点失败\n");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;//一开始我们先把新结点的next置空
//但如果是中间插入或者头插的话,在接口外头我们还要把这个next指向后面的结点
return newNode;
}
//尾插接口
void SListPushBack(SListNode** pphead, SListDataType x) {
//这里怎么尾插
//我们要和顺序表区分开来
//我们是直接把data插进去吗,插到哪里去,有空间插吗
//链表的特点是按需索取
//我们是要先开辟一个空间,放好data,把它放到最后一个结点的后面
//无论链表为空还是不为空,一上来都要创建一个结点
SListNode* newNode = BuySListNode(x);
if (*pphead == NULL) {
//这种情况,我们要先开一个结点
//所以我们开结点的过程直接定义成一个接口
*pphead = newNode;
}
else {
//找尾---遍历
SListNode* tail = *pphead;
while (tail->next != NULL) {
tail = tail->next;//不为空继续往下走
}
//对于链表而言
//不需要扩容这些东西
//按需索取
//对于申请内存来说,顺序表比链表好
tail->next = newNode;//把新结点地址给tail->next即可
}
}
//尾删接口
void SListPopBack(SListNode** pphead) {
//1.空
//2.一个
//3.一个以上
//1.
if (*pphead == NULL) {
return;
}
//2.
else if ((*pphead)->next == NULL) {
free(*pphead);
*pphead = NULL;//记得置空
}
//3.
else {
//关键:把前一个的next置空
SListNode* tail = *pphead;
//prev:前一个结点
SListNode* prev = NULL;
while (tail->next != NULL) {
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;//不要忘记将前一个结点的next置空
}
}
//头插接口
void SListPushFront(SListNode** pphead, SListDataType x) {
SListNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;//记得更改头指针的位置
}
//头删接口
void SListPopFront(SListNode** pphead) {
//1.空
//2.一个结点
//3.一个以上的结点
if (*pphead == NULL) {
return;
}
else {
//怎么删呢?
//1.先free掉第一个-->不行:找不到下一个了
//2.先让pList指向下一个--->不行:找不到我们要free的那个了
//正确的做法:先让一个临时指针充当next的作用,free掉,再让pList指向临时next的位置
//或者用临时指针保存我们要free的,pList先走也可以
//此时我们发现,我们这个代码也可以处理只有一个结点的情况,所以第二种第三种情况合并
SListNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
}
//查找接口
SListNode* SListFind(SListNode* phead, SListDataType x) {
SListNode* cur = phead;
while (cur != NULL) {//遍历过程
if (cur->data == x) {
return cur;
}
cur = cur->next;
}
return NULL;//循环走完找不到,返回空指针
}
//删除pos位置后的节点
void SListEraseAfter(SListNode* pos) {
assert(pos);
if (pos->next) {
//pos->next=pos->next->next;
//不推荐这种写法,这样一些我们原来的pos->next就不见了
SListNode* next = pos->next;
SListNode* next_next = next->next;
pos->next = next_next;
free(next);
}
}
//在pos位置插入节点
void SListInsertAfter(SListNode* pos, SListDataType x) {
assert(pos);//这里是要断言的,因为给的pos不能为空
SListNode* newnode = BuySListNode(x);
newnode->next = pos->next;//先把后面的地址给新结点,再将前一个的next改成新结点地址
pos->next = newnode;
}
3.2 双向链表的实现
3.2.1 List.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}ListNode;
//创建一个结点
ListNode* BuyListNode(LTDataType x);
//初始化
ListNode* ListInit();
//尾插
void ListPushBack(ListNode* phead, LTDataType x);
//头插
void ListPushFront(ListNode* phead, LTDataType x);
//尾删
void ListPopBack(ListNode* phead);
//头删
void ListPopFront(ListNode* phead);
//查找
ListNode* ListFind(ListNode* phead, LTDataType x);
//在pos指针位置(前面)插入
void ListInsert(ListNode* pos, LTDataType x);
//在pos指针位置删除
void ListErase(ListNode* pos);
//判空(空返回1,非空返回0)
int ListEmpty(ListNode* phead);
//链表结点个数
int ListSize(ListNode* phead);
//销毁链表
void ListDestory(ListNode* phead);
//打印
void ListPrint(ListNode* phead);
3.2.2 main.c
#include "List.h"
//测试: 头插、尾插、头删、尾删
void TestList1()
{
/*ListNode* plist;
ListInit(&plist);*/
ListNode* plist = ListInit();
//不需要传二级指针,因为plist始终指向头结点的
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//ListPrint(plist); //1 2 3 4
ListPushFront(plist, 0);
//ListPrint(plist); //0 1 2 3 4
ListPopBack(plist);
//ListPrint(plist); //0 1 2 3
ListPopFront(plist);
ListPrint(plist); //1 2 3
}
void TestList2()
{
ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
ListPushBack(plist, 6);
ListNode* pos = ListFind(plist, 4);
ListInsert(pos, 40);//判断pos是否为空在该函数内部
ListPrint(plist); //1 2 3 40 4 5 6
}
int main()
{
TestList1();
TestList2();
return 0;
}
3.2.3 List.c
#include "List.h"
//创建一个结点
ListNode* BuyListNode(LTDataType x)
{
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
//初始化
//由于是双向带头循环链表,所以此时的next和prev都指向他们自己
ListNode* ListInit()
{
ListNode* phead = BuyListNode(0);
//这样初始化可以让插入操作不需要单独考虑只有phead的情况
phead->next = phead;
phead->prev = phead;
return phead;
}
//尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
//assert(phead);
//找尾
//ListNode* tail = phead->prev;
//ListNode* newnode = BuyListNode(x);
//尾插
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = phead;
//phead->prev = newnode;
//代码复用
//在phead前面插入,相当于尾删
ListInsert(phead, x);
}
//头插
//同样不用考虑只有phead的情况
void ListPushFront(ListNode* phead, LTDataType x)
{
//assert(phead);
//用first记录下第一个结点后,头插时就不需要考虑插入的步骤
//ListNode* first = phead->next;
//ListNode* newnode = BuyListNode(x);
//
//头插 phead newnode first
//phead->next = newnode;
//newnode->prev = phead;
//newnode->next = first;
//first->prev = newnode;
//代码复用
//相当于在head->next之前插入
ListInsert(phead->next, x);
}
//尾删
//不用考虑phead后只有一个结点的情况,但要考虑使用该函数把链表删除到只剩phead时,再调用该函数就会删除phead
void ListPopBack(ListNode* phead)
{
//assert(phead);
//assert(phead->next != phead);//只剩phead的情况
//找到尾结点及其前驱
//ListNode* tail = phead->prev;
//ListNode* tailPrev = tail->prev;
//尾删
//free(tail);
//tailPrev->next = phead;
//phead->prev = tailPrev;
//复用
ListErase(phead->prev);
}
//头删
//也要考虑删除到只有phead的情况
void ListPopFront(ListNode* phead)
{
/*assert(phead);
assert(phead->next != phead);
ListNode* first = phead->next;
ListNode* second = first->next;
free(first);
phead->next = second;
second->prev = phead;*/
//复用
ListErase(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->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
//在pos指针位置删除
void ListErase(ListNode* pos)
{
assert(pos);
//找到pos的前驱后驱
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
//当pos是phead->prev的时候就是尾删
//pos是phead->next就是头删
}
//判空(空返回1,非空返回0)
int ListEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead ? 1 : 0;
}
//链表结点个数
int ListSize(ListNode* phead)
{
assert(phead);
int size = 0;
ListNode* cur = phead->next;
while (cur != phead)
{
size++;
cur = cur->next;
}
return size;
}
//销毁链表
//*注调用后要把phead置空
void ListDestory(ListNode* phead)
{
assert(phead);
//从第一个结点开始销毁
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;//提前保存cur的next,免得释放cur后找不到了
free(cur);
cur = next;
}
//最后把phead销毁,*注意*想销毁phead有两个办法:1.传入二级指针2.用完该函数之后加一句plist = NULL
free(phead);
//phead = NULL;//不起作用,此处选择不用二级指针是为了保证接口的一致性
}
//打印
void ListPrint(ListNode* phead)
{
//cur指向头结点的下一个结点
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
好啦,今天的分享到这里就结束啦 ~
觉得我分享的文章不错的话,可以关注一下哦,嘻嘻嘻
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/73243.html