数据结构(1)—— 线性表

写在前面

为了考研,需要复习数据结构。而对于数据结构这门学科来说,写代码是非常必要的。用代码把一些常见的数据结构与算法实现一遍,非常有利于对于数据结构的理解。

于是今天是第一章,即线性表的实现。主要参考是王道的数据结构复习指导与黑皮的严蔚敏的教材。虽然代码文件后缀都是cpp,但写法还是C的写法,主要借用了cpp里的&bool来方便书写。

静态存储的顺序表实现

注意点

静态存储的顺序表,其实就是用数组来实现,主要注意点在于数组的下标和顺序表的位序并不是一一对应的关系,而是差1(顺序表的位序 = 数组的下标 + 1)。要注意这点。

代码

/*
 * @Description: 静态存储的顺序表实现
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-05 21:39:06
 */
#include<bits/stdc++.h>
#define maxSize 10

// 定义静态存储的顺序表结构
typedef struct{
    int data[maxSize];
    int length;
}SqList;

// 初始化
void initList(SqList &L){
    // 将所有初值设置为0
    for(int i = 0;i < maxSize;i++){
        L.data[i] = 0;
    }
    // 长度设置为0
    L.length = 0;
}

// 插入
bool listInsert(SqList &L,int i,int e){
    // 判断i的范围是否有效
    if(i < 1 || i>L.length+1){
        return false;
    }
    // 判断存储空间是否已经满
    if(L.length >= maxSize){
        return false;
    }
    // 将第i个元素之后的元素后移
    for(int j=L.length;j >= i;j--){
        L.data[j] = L.data[j-1];
    }
    // 在位置i处放入e
    L.data[i-1] = e;
    L.length++;
    return true;
}

// 打印顺序表
void printList(SqList &L){
    printf("顺序表的元素为:\n");
    for(int i = 0;i < L.length; i++){
        printf("%d  ",L.data[i]);
    }
    printf("\n");
}

// 删除
bool listDelete(SqList &L,int i,int &e){
    // 判断i的范围
    if(i < 1 || i > L.length){
        return false;
    }
    // 将被删除的元素赋值给e
    e = L.data[i-1];
    // 将第i个位置后的元素前移
    for(int j=i;j<L.length;j++){
        L.data[j-1] = L.data[j];
    }
    L.length--;
    return true;
}

// 按值查找
int locateElem(SqList L,int e){
    int i;
    for(i = 0;i < L.length;i++){
        if(L.data[i] == e){
            return i+1;
        }
    }
    return 0;
}

// 按位序查找
int getElem(SqList L,int i){
    return L.data[i-1];
}

// 主程序测试
int main(){
    SqList L;
    // 初始化
    initList(L);
    printList(L);
    // 插入几个元素看看
    listInsert(L,1,1);
    listInsert(L,2,2);
    listInsert(L,3,22);
    printList(L);
    // 删除一个元素
    int deleteElem = 0;
    listDelete(L,1,deleteElem);
    printList(L);
    // 查找元素22
    printf("元素22的所在位置为 -> %d\n",locateElem(L,22));
    // 根据位序查找
    printf("位序为1的位置为 -> %d\n",getElem(L,1));
    return 0;
}

动态存储的顺序表实现

注意点

动态存储的顺序表,相当于是在结构体中存储一个指针,当容量不够的时候再去动态申请一波新的空间,把旧的移到新区域就好。其他实现与静态存储的顺序表差别不大。

代码

/*
 * @Description: 动态存储的顺序表实现
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-06 19:18:08
 */
#include<bits/stdc++.h>
#define initSize 3

// 定义动态存储的顺序表结构
typedef struct{
    int *data;
    int maxSize;
    int length;
}SeqList;

// 初始化顺序表
void initList(SeqList &L){
    // 用malloc函数申请一片连续的存储空间
    L.data = (int *)malloc(initSize * sizeof(int));
    // 长度设为0
    L.length = 0;
    // 最大大小设为初始大小
    L.maxSize = initSize;
}

// 动态增加长度
void increaseSize(SeqList &L,int len){
    int *p = L.data;
    // C
    // L.data = (int *) malloc((L.maxSize + len) * sizeof(int));
    // C++
    L.data = new int[L.maxSize + len];
    // 把数据复制到新区域
    for(int i = 0;i < L.length;i++){
        L.data[i] = p[i];
    }
    // 增加长度
    L.maxSize += len;
    // 释放原有空间
    // C
    // free(p);
    // C++
    delete p;
}

// 插入
bool listInsert(SeqList &L,int i,int e){
    // 判断i的范围是否有效
    if(i < 1 || i>L.length+1){
        return false;
    }
    // 判断存储空间是否已经满
    if(L.length >= L.maxSize){
        // 如果已满,申请更多空间
        // 一次多申请几个
        increaseSize(L,5);
    }
    // 将第i个元素之后的元素后移
    for(int j=L.length;j >= i;j--){
        L.data[j] = L.data[j-1];
    }
    // 在位置i处放入e
    L.data[i-1] = e;
    L.length++;
    return true;
}

// 打印顺序表
void printList(SeqList &L){
    printf("顺序表的元素为:\n");
    for(int i = 0;i < L.length; i++){
        printf("%d  ",L.data[i]);
    }
    printf("\n");
}

// 删除
bool listDelete(SeqList &L,int i,int &e){
    // 判断i的范围
    if(i < 1 || i > L.length){
        return false;
    }
    // 将被删除的元素赋值给e
    e = L.data[i-1];
    // 将第i个位置后的元素前移
    for(int j=i;j<L.length;j++){
        L.data[j-1] = L.data[j];
    }
    L.length--;
    return true;
}

// 按值查找
int locateElem(SeqList L,int e){
    int i;
    for(i = 0;i < L.length;i++){
        if(L.data[i] == e){
            return i+1;
        }
    }
    return 0;
}

// 按位序查找
int getElem(SeqList L,int i){
    return L.data[i-1];
}

// 主程序测试
int main(){
    SeqList L;
    // 初始化
    initList(L);
    printList(L);
    // 插入几个元素看看
    listInsert(L,1,1);
    listInsert(L,2,2);
    listInsert(L,3,22);
    printList(L);
    // 此时已满,测试能不能继续插入
    listInsert(L,2,30);
    printList(L);
    // 删除一个元素
    int deleteElem = 0;
    listDelete(L,1,deleteElem);
    printList(L);
    // 查找元素30
    printf("元素30的所在位置为 -> %d\n",locateElem(L,30));
    // 根据位序查找
    printf("位序为1的位置为 -> %d\n",getElem(L,1));
    return 0;
}

带头结点的单链表实现

注意点

这里实现的是带头结点的单链表,不带头结点的会在各个函数上有着区别,要注意这点。对于有头结点的单链表,需要注意判空条件为L -> next == NULL,即头指针指向NULL,否则都为非空。其他需要注意的便是头插法与尾插法的实现了,这块为难点和重点。

代码

/*
 * @Description: 单链表的实现
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-06 20:57:47
 */
#include<bits/stdc++.h>

// 定义带头结点的单链表
typedef struct LNode{
    int data;
    struct LNode *next;
}LNode,*LinkList;

// 初始化单链表
bool initList(LinkList &L){
    // 分配一个头结点
    L = (LNode *) malloc(sizeof(LNode));
    // 内存已满
    if (L == NULL){
        return false;
    }
    // 把头结点指向空
    L -> next = NULL;
    return true;
}


// 判断单链表是否为空
bool empty(LinkList L){
    if(L -> next == NULL){
        return true;
    }
    return false;
}

// 链表输出
void printList(LinkList L){
    LNode *p = L -> next;
    printf("单链表内的元素为:\n");
    while(!empty(p)){
        printf("%d ",p->data);
        p = p->next;
    }
    // 最后一个元素虽指向NULL但仍有值,继续打印
    printf("%d ",p->data);
    printf("\n");
}

// 按位查找,返回第i个元素
LNode * getElem(LinkList L,int i){
    if(i < 0){
        return NULL;
    }
    LNode *p;
    int j = 0;
    p = L;
    while(p != NULL && j < i){
        p = p -> next;
        j++;
    }
    return p;
}

// 按值查找
LNode * locateElem(LinkList L,int e){
    LNode *p = L -> next;
    // 循环遍历链表
    while(p != NULL && p->data !=e){
        p = p->next;
    }
    return p;
}

// 后插,在p结点之后插入元素e
bool insertNextNode(LNode *p,int e){
    // 判断是否超出长度
    if(p == NULL){
        return false;
    }
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if(s == NULL){
        return false;
    }
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}

// 前插,在p结点之前插入元素e,O(1)
bool insertPriorNode(LNode *p,int e){
    if(p == NULL){
        return false;
    }
    LNode *s = (LNode *) malloc(sizeof(LNode));
    if(s == NULL){
        return false;
    }
    // 相当于把p的值给了s,把新的值e给了p,然后连接起来
    s->next = p->next;
    p->next = s;
    s->data = p->data;
    p->data = e;
    return true;
}

// 插入
bool listInsert(LinkList &L,int i,int e){
    if(i < 1){
        return false;
    }
    // 找到第i-1个结点
    LNode *p = getElem(L,i-1);
    // p结点之后插入元素e
    return insertNextNode(p,e);
}

// 删除 O(n)
bool listDelete1(LinkList &L,int i,int &e){
    if(i < 1){
        return false;
    }
    // 查找前驱结点
    LNode *p = getElem(L,i-1);
    // q指向被删除结点
    LNode *q = p->next;
    // 把q从链中断开
    p ->next = q->next;
    // 释放空间
    free(q);
    return true;
}

// 删除 O(1)
// !有坑,在删除最后一个结点时会报错
bool listDelete2(LNode *p){
    if (p == NULL){
        return false;
    }
    LNode *q = p->next;
    p->data = p->next->data;
    p->next = q->next;
    free(q);
    return true;
}

// 求链表长
int getLength(LinkList L){
    int len = 0;
    LNode *p = L;
    while(p -> next != NULL){
        p = p->next;
        len++;
    }
    return len;
}

// 尾插法建立
LinkList listTailInsert(LinkList &L){
    int x;
    // 初始化
    initList(L);
    // 定义头尾指针
    LNode *s = L;
    LNode *r = L;
    scanf("%d",&x);
    while(x != 9999){
        // 在r结点之后插入x
        s = (LNode *) malloc(sizeof(LNode));
        s -> data = x;
        r -> next = s;
        // 让r指针指向新的尾
        r = s;
        scanf("%d",&x);
    }
    r -> next = NULL;
    return L;
}

// 头插法建立
LinkList listHeadInsert(LinkList &L){
    LNode *s;
    int x;
    // 初始化L
    initList(L);
    scanf("%d",&x);
    while(x != 9999){
        // 把新结点插入到头部
        s = (LNode *)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;
        L->next = s;
        scanf("%d",&x);
    }
    return L;
}

// 主函数测试
int main(){
    // 测试尾插法
    LinkList L1,L2;
    listTailInsert(L1);
    printList(L1);
    // 测试头插法
    listHeadInsert(L2);
    printList(L2);
    // 测试插入一个新结点
    listInsert(L1,1,100);
    listInsert(L2,3,200);
    printList(L1);
    printList(L2);
    // 测试删除一个结点
    int deletedNode1;
    listDelete1(L1,3,deletedNode1);
    LNode *deletedNode2 = getElem(L2,3);
    listDelete2(deletedNode2);
    printList(L1);
    printList(L2);
    // 测试查找结点
    printf("在L1中查找100,下一个位置的值为 -> %d",locateElem(L1,100)->next->data);
    return 0;
}

双向链表的实现

注意点

对于双向链表,与单链表不同的是多了一个前向指针,可以实现向前遍历这样的操作了。需要注意的是双向链表的判空条件以及插入时的临界条件。

代码

/*
 * @Description: 双向链表的实现
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-07 21:32:35
 */
#include <bits/stdc++.h>

// 定义双向链表结构
typedef struct DLinkNode
{
    int data;
    // 前向指针与后向指针
    DLinkNode *pre;
    DLinkNode *next;
} DLinkNode, *DLinkList;

// 初始化双链表
bool initList(DLinkList &DL)
{
    // 分配一个头结点
    DL = (DLinkNode *)malloc(sizeof(DLinkNode));
    // 内存已满
    if (DL == NULL)
    {
        return false;
    }
    // 把头结点指向空
    DL->pre = NULL;
    return true;
}

// 创建
DLinkList createDLinkList(DLinkList &DL)
{
    int x;
    initList(DL);
    DLinkNode *p;
    DLinkNode *s;
    p = DL;
    scanf("%d", &x);
    while (x != 9999)
    {
        s = (DLinkNode *)malloc(sizeof(DLinkNode));
        s->data = x;
        p->next = s;
        s->pre = p;
        p = s;
        scanf("%d", &x);
    }
    s->next = NULL;
    return DL;
}

// 打印
void printList(DLinkList DL)
{
    DLinkNode *p = DL->next;
    printf("双链表内的元素为:\n");
    while (p->next)
    {
        printf("%d ", p->data);
        p = p->next;
    }
    // 最后一个元素虽指向NULL但仍有值,继续打印
    printf("%d ", p->data);
    printf("\n");
}

// 头部插入
void insertListHead(DLinkList &DL, int data)
{
    DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
    pNode->data = data;
    pNode->next = DL->next;
    pNode->pre = DL;
    if (pNode->next != NULL)
    {
        pNode->next->pre = pNode;
    }
    DL->next = pNode;
}

// 尾部插入
void insertListTail(DLinkList &DL, int data)
{
    DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
    pNode->data = data;
    DLinkNode *pCur = DL;
    while (pCur->next != NULL)
    {
        pCur = pCur->next;
    }
    pCur->next = pNode;
    pNode->pre = pCur;
    pNode->next = NULL;
}

// 获取链表长度
int getListLength(DLinkList DL)
{
    DLinkNode *p;
    int len = 0;
    p = DL;
    for (; p->next != NULL; p = p->next)
    {
        len++;
    }
    return len;
}

// 指定位置插入节点,前插
bool insertList(DLinkList &DL, int data, int index)
{
    DLinkNode *pCur = DL;
    int len = getListLength(DL);
    if (index > len)
    {
        return false;
    }
    else if (index == 0)
    {
        insertListHead(DL, data);
        return true;
    }
    else if (index == len)
    {
        insertListTail(DL, data);
        return true;
    }

    DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
    pNode->data = data;
    int i = 0;
    while (index--){
        pCur = pCur->next;
    }
    pNode->next = pCur->next;
    pCur->next = pNode;
    pNode->pre = pCur;
    pNode->next->pre = pNode;
    return true;
}

// 删除指定值
bool deleteListNode(DLinkList &DL,int key){
    DLinkNode *pPre = DL;
    DLinkNode *pCur = DL->next;
    while(pCur != NULL){
        if(pCur->data == key){
            if(pCur->next != NULL){
                pCur->next->pre = pCur->pre;
            }
            pCur->pre->next = pCur->next;
            pPre = pCur;
            pCur = pCur->next;
            free(pPre);
            return true;
        }else{
            pPre = pCur;
            pCur = pCur->next;
        }
    }
    return false;
}

// 主函数测试
int main(){
    DLinkList DL;
    // 创建
    createDLinkList(DL);
    printList(DL);
    // 从头插入一个值
    insertListHead(DL,5);
    printList(DL);
    // 从尾插入一个值
    insertListTail(DL,100);
    printList(DL);
    // 任意位置插入
    insertList(DL,1000,2);
    printList(DL);
    // 删除值为100的结点
    deleteListNode(DL,100);
    printList(DL);
}

带头结点的单循环链表的实现

注意点

这里实现的是带头结点的单循环链表,循环链表在理解了其本质后其实是很好写的。主要就是把最后一个元素的next指针指向了第一个元素。关于实现,需要注意循环链表的判空条件,以及插入时的操作。

代码

/*
 * @Description: 循环单链表的实现
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-08 19:38:04
 */

#include<bits/stdc++.h>

// 定义循环单链表结构体
typedef struct CLNode{
    int data;
    CLNode *next;
}CLNode,* CLinklist;

// 初始化
bool initList(CLinklist &CL){
    CL = (CLNode *)malloc(sizeof(CLNode));
    if(CL == NULL){
        return false;
    }
    // 循环链表初始化头结点指向本身
    CL->next=CL;
    return true;
}

// 判空
bool isEmpty(CLinklist CL){
    // 指向本身即为空
    if(CL->next == CL){
        return true;
    }
    return false;
}

// 长度
int getLength(CLinklist CL){
    CLNode *p = CL->next->next;
    int len = 0;
    // p未到表头时
    while(p != CL->next){
        len++;
        p = p->next;
    }
    return len;
}

// 取元素
int getElem(CLinklist CL,int i,int *e){
    CLNode *p = CL->next;
    int j = 0;
    if(i < 1 || i > getLength(CL)){
        // 不合法,返回-1
        return -1;
    }
    while(j < i){
        j++;
        p = p->next;
    }
    *e = p->data;
    // 返回0代表成功
    return 0;
}

// 定位元素
int locateElem(CLinklist CL,int e){
    CLNode *p = CL->next->next;
    int j = 0;

    while(p != CL->next){
        j++;
        if(p->data == e){
            return j;
        }
        p = p->next;
    }
    return -1;
}

// 插入元素
bool listInsert(CLinklist &CL,int i,int e){
    CLNode *p = CL->next;
    CLNode *s;
    int j = 0;
    if(i < 1 || i > getLength(CL) + 1){
        return false;
    }
    while(j < i -1){
        j++;
        p = p->next;
    }
    s = (CLNode *)malloc(sizeof(CLNode));
    s->data = e;
    s->next = p->next;
    p->next = s;
    // 如果在表尾插入,则把新插入的元素当做头结点(第一个元素)
    if(p == CL){
        CL = s;
    }
    return true;
}

// 删除元素
bool listDelete(CLinklist &CL,int i,int &e){
    CLNode *p = CL->next;
    CLNode *q;
    int j = 0;
    if(i < 1 || i > getLength(CL)){
        return false;
    }
    while(j < i - 1){
        j++;
        p = p->next;
    }
    q = p->next;
    e = q->data;
    p->next = q->next;
    // 删除表尾元素,指针发生变化
    if(q == CL){
        CL = p;
    }
    free(q);
    return true;
}

// 打印
void printList(CLinklist CL)
{
    CLNode *p = CL->next->next;
    printf("循环链表内的元素为:\n");
    while (p != CL->next)
    {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}

// 主函数测试
int main(){
    CLinklist CL;
    initList(CL);
    printList(CL);
    // 插入几个元素
    listInsert(CL,1,100);
    listInsert(CL,2,2);
    listInsert(CL,3,3);
    listInsert(CL,4,4);
    listInsert(CL,5,5);
    printList(CL);
    // 插入到表尾试试
    listInsert(CL,5,1000);
    printList(CL);
    // 删除一个元素
    int deletedElem;
    listDelete(CL,3,deletedElem);
    printList(CL);
}

总结

这里还缺双向循环链表的实现,其实这种实现也并不是很难的方式,读者可以去自行实现。在线性表这一块大量使用了指针,一定要好好理解指针的概念才能更好地实现代码。

上一篇:PHP简用


下一篇:Sers微服务快速入门-02.快速接入