【数据结构】——顺序表和链表(轻松入门)

 🍺🍺哈喽,大家好丫,你们的小郭子又来啦 ~

话不多说,直接上干货,嘻嘻嘻 ~

                        

目录

1. 顺序表和链表的区别和联系

1.1 顺序表的优点

1.2 顺序表的缺点

1.3 链表的优点

1.4 链表的缺点

2 .顺序表

2.1 定义

2.2  顺序表的储存

2.3  顺序表的实现

2.3.1 SeqList.h

2.3.2 main.c

2.3.3 SeqList.c

3.链表

3.1 单链表的实现

3.1.1 SList.h

3.1.2 main.c

3.1.3 SList.c

3.2 双向链表的实现

3.2.1 List.h

3.2.2 main.c

3.2.3 List.c


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");
}

好啦,今天的分享到这里就结束啦 ~

觉得我分享的文章不错的话,可以关注一下哦,嘻嘻嘻

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值