zoukankan      html  css  js  c++  java
  • 数据结构(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("顺序表的元素为:
    ");
        for(int i = 0;i < L.length; i++){
            printf("%d  ",L.data[i]);
        }
        printf("
    ");
    }
    
    // 删除
    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
    ",locateElem(L,22));
        // 根据位序查找
        printf("位序为1的位置为 -> %d
    ",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("顺序表的元素为:
    ");
        for(int i = 0;i < L.length; i++){
            printf("%d  ",L.data[i]);
        }
        printf("
    ");
    }
    
    // 删除
    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
    ",locateElem(L,30));
        // 根据位序查找
        printf("位序为1的位置为 -> %d
    ",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("单链表内的元素为:
    ");
        while(!empty(p)){
            printf("%d ",p->data);
            p = p->next;
        }
        // 最后一个元素虽指向NULL但仍有值,继续打印
        printf("%d ",p->data);
        printf("
    ");
    }
    
    // 按位查找,返回第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("双链表内的元素为:
    ");
        while (p->next)
        {
            printf("%d ", p->data);
            p = p->next;
        }
        // 最后一个元素虽指向NULL但仍有值,继续打印
        printf("%d ", p->data);
        printf("
    ");
    }
    
    // 头部插入
    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("循环链表内的元素为:
    ");
        while (p != CL->next)
        {
            printf("%d ", p->data);
            p = p->next;
        }
        printf("
    ");
    }
    
    // 主函数测试
    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);
    }
    

    总结

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

  • 相关阅读:
    location.replace与location.href,location.reload的区别
    转载关于KeyPress和KeyDown事件的区别和联系
    Javascript中call的使用
    按值和按引用的比较
    理解cookie的path和domain属性
    HTML的快速写法:Emmet和Haml
    SVN标准命令
    linux常用命令
    Android4.0(Phone)拨号启动过程分析(一)
    Activity生命周期
  • 原文地址:https://www.cnblogs.com/wushenjiang/p/14992352.html
Copyright © 2011-2022 走看看