链表主要操作集的实现

  • 相关宏定义及数据类型的别名定义

    #define OK 1
    #define ERROR -1
    
    #define EMPTY 0
    #define NOT_EMPTY 1
    
    typedef int ElemDataType;
    typedef int Status;
    typedef int Length;
    typedef int Position;
    
    
  • 结构定义

    // 链表数据元素结点:存储元素值及下一结点的地址
    typedef struct LNode {
        ElemDataType data;
        struct LNode *next;
    } ElemType,  *Link;
    
    // 存储链表基本信息的结构:包括链表头、尾结点的地址及链表长度信息
    typedef struct {
        Link head, tail;
        int length;
    } LinkList;
    
  • InitList():构造一个带头结点的空表

    // 初始化链表结构,该链表为带头结点(内容为空)的单链表 
    Status InitList_L(LinkList *L) {
        ElemType *emptyNode;
        emptyNode = (ElemType *)malloc(sizeof(ElemType));
        
        // 若分配失败 则返回异常
        if (!emptyNode) 
            return ERROR;
        
        // 指针域置空
        emptyNode->next = NULL;
        
        // 空表,头尾结点均指向数据域、指针域都为空的头结点
        (*L).head = (*L).tail = emptyNode;
        
        // 链表长度为 0
        (*L).length = 0;
    
        return OK;
    }
    

    有了头结点后,对在第一个结点前插入新结点,和删除第一个结点,其操作与对其他结点的操作可统一起来

  • CreateList(): 建立链表

    • 正向建立链表(表头至表尾)
    // 正向建立链表
    Status CreateList_L_ProperOrder(LinkList *L, int size) {
        InitList_L(L);
        ElemType *newNode, *p = (*L).head;
        
        // size: 欲创建的结点个数
        printf("Input %d nodes: ", size);
    
        for (int i = 0; i < size; ++i) {
            newNode = (ElemType *)malloc(sizeof(ElemType));
            
            if (!newNode)   
                return ERROR;
            
            // 获取用户输入,将其赋给新结点的数据域
            scanf_s("%d", &newNode->data, 1);
    
            p->next = newNode;  // 将新结点接入尾结点之后 
            p = p->next;    // 使 p 指向新的尾结点
            p->next = NULL; // 表尾结点指针域置空
            (*L).tail = p;  // 更新链表中的尾指针域,使其指向最新的尾结点
    
            (*L).length++;  // 表长度+1
        }
        return OK;
    }
    
    • 逆向建立链表(表尾至表头)
    // 逆向建立链表
    Status CreateList_L_ReverseOrder(LinkList *L, int size) {
        InitList_L(L);
        Link newNode;
        printf("Input %d nodes: ", size);
    
        for (int i = 0; i < size; ++i) {
            newNode = (ElemType *)malloc(sizeof(ElemType));
    
            if (!newNode)   
                return ERROR;
    
            scanf_s("%d", &newNode->data, 1);
    
            newNode->next = (*L).head->next;    // 将新结点插入到第一个结点(头结点后的结点)之前
            (*L).head->next = newNode;  // 新结点成为新的第一个结点
            (*L).length++;  // 长度+1
    
            //  倒序建立链表,第一次连入链表的结点即为尾结点
            if (i == 0) {
                (*L).tail = newNode;    //  链表尾指针指向第一个连入的结点,从此再不变化
            }
        }
        return OK;
    }
    
  • DestroyList(): 销毁线性表

    Status DestroyList_Sq(SqList *L) {
        // 若链表不存在头结点,则表明尚未初始化,无需销毁
        if (!(*L).head) 
            exit(EXIT_FAILURE);     
    
        // 先将链表置空,释放所有含有效数据的结点
        ClearList_L(L);
    
        // 释放头结点
        FreeNode(&((*L).head));
    
        (*L).head = (*L).tail = NULL;
    
        return OK;
    }
    
  • ClearList():重置为空表

    // 将链表置空,并释放原链表的节点空间,即清除所有含有效数据的结点
    Status ClearList_L(LinkList *L) {
        if ((*L).head == (*L).tail) 
            return ERROR;
    
        Link p = (*L).head->next;       //  p 初始指向第一个结点
        Link DelNode = NULL;
        (*L).head->next = NULL;     //  将头结点与第一个结点的连接切断
    
        // 依次释放各结点的空间
        while (p != NULL) {
            DelNode = p;
            *p = *p->next;
            FreeNode(&DelNode); 
        }
    
        (*L).tail = (*L).head;
        (*L).length = 0;
    
        return OK;
    }
    
  • ListEmpty(): 检测该表是否为空

    Status ListEmpty_L(LinkList *L) {
        return L->length == 0 ? EMPTY : NOT_EMPTY;
    }
    
  • GetLength(): 获取顺序表中元素的个数

    Length GetLength_L(LinkList *L) {
        return (*L).length;
    }
    
  • GetHeadNodeVal(): 返回第一个非空节点的值

        ElemDataType GetHeadNodeVal(LinkList *L) {
            return (*L).head->next->data;
        }
    
  • GetTailNodeVal(): 获取尾结点的值

    ElemDataType GetTailNodeVal(LinkList *L) {
        return (*L).tail->data;
    }
    
  • GetLength_L(): 获取链表长度

    Length GetLength_L(LinkList *L) {
        return (*L).length;
    }
    
  • GetElem(): 用 e 返回 L 中第 i 个结点的值

    Status GetElem_L(LinkList *L, int i, ElemDataType *e) {
        if (L->head->next == NULL) {
            printf("ERROR: 该链表为空!");
            return ERROR;
        }
    
        if (i < 1 || i > L->length) {
            printf("ERROR: 目标位置: %d 错误!", i);
            return ERROR;
        }
    
        Link p = L->head->next;
        int j = 1;
    
        while (p && j < i) {
            p = p->next;
            j++;
        }
    
        if (j == i) {
            *e = p->data;
        } 
        // 未找到链表中第 i 个位置的结点
        else {
            e = NULL;
            printf("ERROR: 未找到目标结点!");
            return ERROR;
        }
    
        return OK;
    }
    
  • LocateElem(): 返回表中第一个与 e 值满足关系 compare() 的元素的位置

    Position LocateElem_L(LinkList *L, ElemType e, Status((*compare)(ElemDataType e1, ElemDataType e2))) {
        if (ListEmpty_L(L) == EMPTY) {
            printf("ERROR: 表不能为空!\n");
            return ERROR;
        }
    
        int i = 1;
        Link p = L->head->next;
    
        while (p) {
            if ((*compare)(e.data, p->data) == OK) {
                return i;
            }
            else {
                i++;
                p = p->next;
            }
        }
    
        return 0;
    }
    
    
  • compare(): 这里使用相等关系表示 compare() 函数

    Status Equal(ElemDataType e1, ElemDataType e2) {
        return (e1 == e2) ? OK : ERROR;
    }
    
    • 调用举例
    int main() {
        ...
        // 返回表中第一个值为 8 的元素的位序
        int pos = LocateElem_Sq(&L, 8,  Equal);
        
        // 例如对于顺序表:{3, 8, 6, 0, 9, 7, 4, 11},此时 pos 的值应为 2
        printf("%d", pos);
        ...
    }
    
  • GetKth(): 获取第 k 个元素

    Status GetKth(LinkList *L, int k, ElemType *e) {
        Link p = L->head->next;
        int i = 1;
    
        while (p && i < k) {
            p = p->next;
            i++;
        }
    
        if (i == k) {
            *e = *p;
            return OK;
        }
        else {
            return ERROR;
        }
    }
    
  • PriorElem(): 获取 curElem 的前驱,用 pre_e 返回

    ElemType PriorElem_Sq(SqList *L, ElemType curElem, ElemType *pre_e) {
        // 先找到当前元素的位序
        int locateRes = LocateElem_Sq(L, curElem, Equal);
    
        // ERROR:表空; 1: 位序为1,第1个元素无前驱; 0: 表中未找到当前元素
        if (locateRes == ERROR || locateRes == 1 || locateRes == 0) {
            if (locateRes == 1) {
                printf("\nERROR: 第一个元素无前驱!");
            }
            else if (locateRes == 0) {
                printf("\nERROR: 目标元素: %d 不存在!", curElem);
            }
    
            return ERROR;
        }
    
        // 成功找到存在前驱的 curElem
        else {
            //  将位序为 locateRes - 1 的元素(即curElem的前驱)值写入 pre_e
            if (GetKth(L, locateRes - 1, pre_e) != ERROR) {
                return OK;
            }
            else {
                printf("\nERROR: 未找到指定元素: %d 的前驱!", curElem.data);
                return ERROR;
            }
        }
    }
    
  • NextElem(): 获取 curElem 的后继,用 next_e 返回

    ElemType NextElem_Sq(SqList *L, ElemType curElem, ElemType *next_e) {
        int locateRes = LocateElem_Sq(L, curElem, Equal);
    
        // ERROR:表空; 1: 位序为length,最后1个元素无后继; 0: 表中未找到当前元素
        if (locateRes == ERROR || locateRes == L->length || locateRes == 0) {
            if (locateRes == L->length) {
                printf("\nERROR: 最后一个元素无后继!");
            }
            else if (locateRes == 0) {
                printf("\nERROR: 目标元素: %d 不存在!", curElem);
            }
            return ERROR;
        }
        else {
            // 将位序为 locateRes + 1 的元素(即curElem的后继)值写入 next_e
            if (GetKth(L, locateRes + 1, next_e) != ERROR) {
                return OK;
            }
            else {
                printf("\nERROR: 未找到指定元素: %d 的前驱!", curElem.data);
                return ERROR;
            }
        }
    }
    
  • 调用举例:查看表中所有元素的前驱与后继

    int main () {
        ...
        
        Link p = L1.head->next;
    
        while (p) {
            ElemType cur_e = *p;
            ElemType *pre_e = (ElemType *)malloc(sizeof(ElemType));
            ElemType *next_e = (ElemType *)malloc(sizeof(ElemType));
    
            if (PriorElem_L(&L1, cur_e, pre_e) == ERROR) {
                pre_e->data = -1;
            }
            if (NextElem_L(&L1, cur_e, next_e) == ERROR) {
                next_e->data = -1;
            }
    
            printf("\npreElem: %d\ncurElem: %d\nnextElem: %d\n", pre_e->data, cur_e.data, next_e->data);
    
            FreeNode(&pre_e);
            FreeNode(&next_e);
    
            p = p->next;
        }
        
        ...
    }
    
    
  • Append(): 将一链表添加至指定链表的最后

    // 将 S 所指的一串结点链接在 L 的最后一个结点
    Status Append(LinkList *L, LinkList S) {
        if (ListEmpty_L(&S) == EMPTY) {
            return ERROR;
        }
    
        (*L).tail->next = S.head->next; // 将 S 的第一个非空节点接入 L 的尾结点
        (*L).tail = S.tail; // 更新 L 尾结点的指向
        (*L).length += S.length;    // 更新 L 的表长
    
        S.head->next = NULL;    // 断开头结点与 S 第一个非空节点的连接
        S.tail = NULL;  // 尾结点置空
        S.length = 0;   // 表长清零
        FreeNode(&(S.head));    // 释放 S 的头结点空间
    
        return OK;
    }
    
  • LinkListInsBefore(): 在表中第 i 个结点之前插入数据域为 e 的新结点

    Status LinkListInsBefore(LinkList *L, int i, ElemDataType e) {
        // i = length + 1 时,等同于在表尾添加结点
        if (i <= 0 || i > (*L).length + 1)  {
            printf("\nERROR: 插入位置: %d 不合法!", i);
            return ERROR;
        }
    
        ElemType *p = (*L).head, *newNode;
        int j = 0;
    
        //  j 从 0 至 i - 2,循环共进行 i - 1 次,p 从头结点向后移动 i - 1 次,循环结束后,p 指向的为第 i - 1 个结点,即被插入的结点的前驱
        while (j < i - 1) {
            p = p->next;
            ++j;
        }
    
        newNode = (ElemType *)malloc(sizeof(ElemType));
    
        newNode->next = p->next;    // 使新结点指向第 i 个结点
        p->next = newNode;  // 使第 i 个结点的前驱指向新结点
        newNode->data = e;  // 为新结点的数据域赋值
    
        (*L).length++;  // 表长+1
    
        //  在表尾添加结点,链表结构中尾指针需更新
        if (p == (*L).tail) {
            (*L).tail = newNode;
        }
    
        return OK;
    }
    
  • LinkListInsAfter(): 在第 i 个结点之后插入数据域为 e 的新结点

    Status LinkListInsAfter(LinkList *L, Position i, ElemDataType e) {
        LinkListInsBefore(L, i+1, e);
        return OK;
    }
    
  • ListDelete(): 删除第 i 个元素 并返回它的值

    // 删除第 i 个元素
    Status ListDelete_L(LinkList *L, int i, ElemDataType *e) {
        if (i < 1 || i >(*L).length)    
            exit(ERROR);
        if ((*L).length < 1)    
            exit(ERROR);
        
        Link p = (*L).head;
    
        for (int j = 0; j < i - 1; ++j) {
            p = p->next;        //  p 指向的是被删除元素的前驱,即:被删除的元素指针是 p->next
        }
        
        *e = p->next->data;
        p->next = p->next->next;
    
        (*L).length--;      //  长度 - 1
        free(p->next);  //  释放被删除的节点空间
    
        //  经删除操作后,若p 的指针域为空,说明删除的是表尾元素,尾指针需更新
        if (!(p->next)) 
            (*L).tail = p;
    
        return OK;
    }
    
  • ListTraverse(): 对表中每个元素进行遍历

    Status ListTraverse_L(LinkList *L, Status((*visit)(ElemType *curElem))) {
        if (ListEmpty_L(L) == EMPTY)
            return ERROR;
    
        Link p = L->head->next;
    
        while (p) {
            if ((*visit)(p) == ERROR) {
                exit(EXIT_FAILURE);
            }
            p = p->next;
        }
    
        printf("\n");
        return OK;
    }
    
    
  • visit(): 这里使用打印输出元素的值表示对单个元素的访问

    Status PrintElem(ElemType *curElem) {
        if (!curElem)
            return ERROR;
    
        printf("%d ", curElem->data);
        return OK;
    }
    
    
    • 调用举例
    int main() {
        ...
        // 以 PrintElem 作为 Visit 方法遍历顺序表 L,程序将打印输出 L 中每个元素的值
        ListTraverse_Sq(&L, PrintElem);
        ...
    }
    
  • MergeList(): 将两个有序链表(升序)合并为一个新链表

    Status MergeList_L(LinkList *L1, LinkList *L2, LinkList *L3) {
        if ((*L1).length < 1 || (*L2).length < 1)   
            exit(EXIT_FAILURE);
    
        Link pa, pb, pc;
        pa = (*L1).head->next;
        pb = (*L2).head->next;
        
        // L3 无需新分配空间,不妨以 L1 的头指针作为起点,通过改变当前结点的指针域来达到按序合并的效果
        *L3 = *L1;
        
        // pc 一开始指向的是 L1 的头结点(数据域为空)
        pc = (*L3).head;
    
        while (pa && pb ) {
            if (pa->data <= pb->data) {
                pc->next = pa;
                pc = pa;
                pa = pa->next;
            } else {
                pc->next = pb;
                pc = pb;
                pb = pb->next;
            }
        }
        
        pc->next = pa ? pa : pb;
    
        (*L3).length = (*L1).length + (*L2).length;
        (*L3).tail = pa ? (*L1).tail : (*L2).tail;
    
        return OK;
    }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容