带头结点的单链表实现(C语言)

带头结点的单链表实现(C语言)

代码的大致结构如下:

1.声明库函数
2.定义全局变量
3.命名
4.数据结构的定义
5.相关操作的声明
6.main函数(用来测试函数)
7.相关操作的实现

代码的具体实现如下:

代码中每个地方都加入了较为详细的注释,这里就不再解释了

/*
 * 带头结点的单链表实现
 */
#include <stdio.h>
#include <stdlib.h>
/*
 * 全局变量声明
 */
#define true 1//代表函数的执行状态为成功,没有出错
#define false 0//代表函数的执行状态为失败,出错
#define overflow -1//内存溢出,代表没有足够的内存空间

/*
 * 返回值和元素类型命名
 */
typedef int Status;//代表函数返回值的状态
typedef int ElemType;//代表元素类型

/*
 * 定义单链表的结点类型
 * LNode用于声明结点
 * LinkList用于声明单链表,注意是指针类型
 */
typedef struct LNode {
    ElemType data;//结点的数据域,用于存放数据
    struct LNode *next;//结点的指针域,用于存放下一个结点的地址
} LNode, *LinkList;

/*
 * 带头结点的单链表基本操作的函数声明
 */
// 1. 单链表的初始化
Status List_Init(LinkList *L);

// 2. 单链表的建立---头插法
LinkList List_HeadInsert(LinkList L);

// 3. 单链表的建立---尾插法
LinkList List_TailInsert(LinkList L);

// 4. 单链表的销毁
Status List_Destroy(LinkList *L);

//*****************插入操作*********************

// 5. 单链表的第i个位置插入结点
Status List_InsertElem(LinkList L, int i, ElemType e);

// 6. 单链表指定结点的后插操作
Status List_InsertNextNode(LNode *p, ElemType e);

// 7. 单链表的指定结点前插入操作1
Status List_InsertPreNodeFirst(LinkList L, LNode *p, ElemType e);

// 8. 单链表的指定结点前插入操作2
Status List_InsertPreNodeSecond(LNode *p, ElemType e);

//************删除操作****************

// 9. 单链表的第i个结点删除
Status List_DeleteElem(LinkList L, int i, ElemType *e);

// 10. 单链表的指定结点的删除1
Status List_DeleteNodeFirst(LinkList L, LNode *p, ElemType *e);

// 11. 单链表的指定结点的删除2
Status List_DeleteNodeSecond(LNode *p, ElemType *e);

// 12. 单链表指定结点的后一个结点删除
Status List_DeleteElemNextNode(LNode *p, ElemType *e);

//**************************

// 13. 单链表的判空
Status List_IsEmpty(LinkList L);

// 14. 单链表的长度,第一种方法
int List_LengthFirst(LinkList L);

// 15. 单链表的长度,第二种长度
int List_LengthSecond(LinkList L);

// 16. 单链表的查找---按位查找
LNode *List_GetElem(LinkList L, int i);

// 17. 单链表的查找---按值查找
LNode *List_LocateElem(LinkList L, ElemType e);

// 18. 单链表的打印操作
Status List_PrintElem(LinkList L);

// 19. 两个单链表的合并1
LinkList List_MergeFirst(LinkList L1, LinkList L2, LinkList L3);

// 20. 两个单链表的合并2
LinkList List_MergeSecond(LinkList L1, LinkList L2);

// 21. 单链表的清空
Status List_Clear(LinkList L);

int main() {
    LinkList L1, L2, L3;
    LinkList L;
    printf("单链表的判空:%d\n", List_IsEmpty(L));
    printf("单链表的初始化执行状态:%d\n", List_Init(&L));
    printf("单链表的判空:%d\n", List_IsEmpty(L));
    printf("***********\n");
    List_HeadInsert(L);
    printf("单链表的头插法建立:%d\n", List_PrintElem(L));
    printf("单链表的判空:%d\n", List_IsEmpty(L));
    printf("当前单链表的长度1:%d\n", List_LengthFirst(L));
    printf("当前单链表的长度2:%d\n", List_LengthSecond(L));
    printf("单链表的销毁:%d\n", List_Destroy(&L));
    printf("*********************\n");
    List_Init(&L);
    List_TailInsert(L);
    printf("单链表的尾插法建立:%d\n", List_PrintElem(L));
    printf("当前单链表的长度1:%d\n", List_LengthFirst(L));
    printf("当前单链表的长度2:%d\n", List_LengthSecond(L));
    printf("*********************\n");
    LNode *p = List_GetElem(L, 4);
    printf("单链表按位查找:%d\n", p->data);
//    LNode *s = List_GetElem(L, -1);
//    printf("单链表按位查找异常情况1:%d\n", s->data);
//    printf("单链表按位查找异常情况2:%d\n", s->data);
    LNode *q = List_LocateElem(L, 17);
    printf("单链表按值查找:%d\n", q->data);
//    LNode *s = List_LocateElem(L, 67);
//    printf("单链表按位查找异常情况:%d\n", s->data);
    printf("*********************\n");
    p = List_GetElem(L, 4);
    printf("单链表的元素插入:%d\n", List_InsertElem(L, 4, 23));
    printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
    printf("当前单链表的长度:%d\n", List_LengthFirst(L));
    p = List_GetElem(L, 4);
    printf("p结点前插结点1:%d\n", List_InsertPreNodeFirst(L, p, 90));
    printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
    printf("当前单链表的长度:%d\n", List_LengthFirst(L));
    p = List_GetElem(L, 4);
    printf("p结点前插结点2:%d\n", List_InsertPreNodeSecond(p, 67));
    printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
    printf("当前单链表的长度:%d\n", List_LengthFirst(L));
    p = List_GetElem(L, 4);
    printf("单链表的后插操作:%d\n", List_InsertNextNode(p, 34));
    printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
    printf("当前单链表的长度:%d\n", List_LengthFirst(L));
    printf("*************\n");
    ElemType e;
    p = List_GetElem(L, 4);
    printf("单链表的删除结点:%d", List_DeleteElem(L, 4, &e));
    printf(",删除结点的值:%d\n", e);
    printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
    printf("当前单链表的长度:%d\n", List_LengthFirst(L));
    p = List_GetElem(L, 4);
    printf("单链表p结点的删除1:%d", List_DeleteNodeFirst(L, p, &e));
    printf(",删除结点的值:%d\n", e);
    printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
    printf("当前单链表的长度:%d\n", List_LengthFirst(L));
    p = List_GetElem(L, 4);
    printf("单链表p结点的删除2:%d", List_DeleteNodeSecond(p, &e));
    printf(",删除结点的值:%d\n", e);
    printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
    printf("当前单链表的长度:%d\n", List_LengthFirst(L));
    p = List_GetElem(L, 4);
    printf("单链表p结点后继的删除:%d", List_DeleteElemNextNode(p, &e));
    printf(",删除结点的值:%d\n", e);
    printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
    printf("当前单链表的长度:%d\n", List_LengthFirst(L));
    List_Destroy(&L);
    printf("***************\n");
    List_Init(&L1);
    List_Init(&L2);
    List_Init(&L3);
    List_HeadInsert(L1);
    List_TailInsert(L2);
    printf("单链表L1的打印执行状态:%d\n", List_PrintElem(L1));
    printf("当前单链表L1的长度:%d\n", List_LengthFirst(L1));
    printf("单链表L2的打印执行状态:%d\n", List_PrintElem(L2));
    printf("当前单链表L2的长度:%d\n", List_LengthFirst(L2));
    printf("单链表L3的打印执行状态:%d\n", List_PrintElem(L3));
    printf("当前单链表L3的长度:%d\n", List_LengthFirst(L3));
    printf("___________\n");
    printf("单链表的合并1\n");
//    List_MergeFirst(L1,L2,L3);
//    printf("单链表的打印执行状态:%d\n", List_PrintElem(L3));
//    printf("当前单链表的长度:%d\n", List_LengthFirst(L3));
    printf("单链表的合并2\n");
    List_MergeSecond(L1, L2);
    printf("单链表的打印执行状态:%d\n", List_PrintElem(L1));
    printf("当前单链表的长度:%d\n", List_LengthFirst(L1));
//
//    printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
//    printf("当前单链表的长度:%d\n", List_LengthFirst(L));


    return 0;
}

/*
 * 带有结点的单链表的基本操作的函数实现:
 *
 * 可能的问题:
 * 1.函数中很多的返回值是true或者false,一些题目都是void,没有返回值:
 *      这么写是为了方便测试bug,容易测出来是哪个功能出了问题,帮助快速定位
 *      考试题目中全部换成void类型,不需要返回值
 *      void StaticInitSqList(SqList *L) {
 *             *********
 *             //不需要return
 *       }
 */

// 1. 单链表的初始化
Status List_Init(LinkList *L) {
    /*
     * 时间复杂度O(1)
     */
    /*
     * 这里形式参数使用二级指针,指针的指针,也就是指向指针的指针
     * 因为声明L的时候,L一级指针,指向的是某一块空间,但是一级指针只能修改所指内容的值,不能修改自身的值
     * 不使用二级指针的话,下面创建头结点的操作会报错,因为不能改变L的值,L的值是某个地方的地址
     * 如果要修改L自身的值,就需要使用二级指针指向一级指针,通过对二级指针操作达到修改头指针的目的
     *
     * 使用二级指针的情况:
     * 1、单链表的初始化和销毁
     * 2、单链表的其他操作,如插入、删除、遍历,等等操作使用一级指针就好
     */
//    创建单链表的头结点
    *L = (LinkList) malloc(sizeof(LNode));
//    判断头结点是否创建成功
    if (!(*L)) {
//        头结点创建失败,但内存空间不够的情况下可能会出现这个情况,一般情况下没有这个问题
        return false;
    }
//    将头结点的指针域指向空,代表为空链表
    (*L)->next = NULL;
//    返回函数执行成功的状态
    return true;
}

// 2. 单链表的建立---头插法
LinkList List_HeadInsert(LinkList L) {
    /*
     * 时间复杂度O(n)
     */
//    声明s指针用来指向新节点
    LNode *s;
//    这里直接指定存放的值,不进行人为输入了
    int i;//声明循环变量
    for (i = 10; i >= 0; --i) {
        s = (LNode *) malloc(sizeof(LNode));
//        这里没有判断s结点内存是否分配成功,默认都分配成功。其实加上判断也可以
/*
 *      if(!s){
 *          return false;
 *      }
 *      这里函数返回值是单链表类型,不能写这个,想写这个的话,需要把返回值类型改成STATUS
 */
        s->data = i + 1;
//        新结点指向头结点的下一个位置,也就是首元结点
//        如果是第一个结点的话,s指向的是空,在初始化的时候已经让头结点的指针域为空,所以这里不需要变动
        s->next = L->next;
//        新结点作为第一个结点,也就是首元结点,头结点指向新节点
        L->next = s;
    }
//    这里其实用void也可以或者写成return true,因为使用的是指针,可以直接修改,不需要通过返回值达到修改的目的
    return L;
}

// 3. 单链表的建立---尾插法
LinkList List_TailInsert(LinkList L) {
    /*
     * 时间复杂度O(n)
     */
//    声明s指针用来指向新分配的结点
    LNode *s;
//    声明并初始化尾指针r,刚开始尾指针指向头结点,由于使用尾插法,所以需要标记尾结点的位置
    LNode *r = L;
    int i;//声明循环变量
    for (i = 10; i < 20; ++i) {
//        让s指针指向新分配的结点,插入到最后
        s = (LNode *) malloc(sizeof(LNode));
//        这里没有异常判断s结点是否分配成功,默认都是分配成功
        s->data = i + 1;
//        让尾指针指向结点的指针域指向s结点,使得s指向的结点为最后一个结点
        r->next = s;
//        移动r指针到s指向的结点,使r指针指向的是最后一个结点
        r = s;
    }
//    在结点插入完成后,将尾指针指向结点的指针域指向NULL
//  循环里没有写是因为会在尾部一直插入结点,这会让这行代码多余
    r->next = NULL;
//    和头插法一样,不多说了
    return L;
}

// 4. 单链表的销毁
Status List_Destroy(LinkList *L) {
    /*
     * 时间复杂度O(n)
     */
//    声明并初始化一个p指针指向头结点的下一个位置,也就是首元结点
//     这里注意二级指针,看初始化的方法
    LNode *p = (*L)->next;
//    声明q指针,用来指向 p指向的即将被删除的结点,因为直接释放p所指的结点的话,会导致p指针无法移动到下一个位置
    LNode *q;
    while (p != NULL) {
//        q指针指向p所指的待删除的结点
        q = p;
//        将p指针移动到下一个位置
        p = p->next;
//        释放q所指的结点
        free(q);
    }
//    其他结点都释放完成后,释放头结点
    free(*L);
    return true;
}

// 5. 单链表的第i个位置插入结点
Status List_InsertElem(LinkList L, int i, ElemType e) {
    /*
     * 时间复杂度O(n)
     */
/*
 *     对i的值的合法性进行判断,i的合法范围[1,length+1]
 *     这里没有进行判断是因为如果i值不合法查找到的i-1位置的结点也不合法,后面会因为p结点判空不符合条件结束
 *     这里也可以加入i>List_LengthFirst(L)+1和i<1的判断,这样的话保证了i的合法性,后面p结点的判空就可以不写了
 *     不过考试题目的话最好写上,保险,这里不写是因为冗余了
 *     if(i>List_LengthFirst(L)+1||i<1){
 *          return false;
 *     }
 *     写法很多,不唯一
 */
//    p指向i-1位置的结点,这里有对i-1位置的合法性判断
    LNode *p = List_GetElem(L, i - 1);
//    对i-1位置的结点是否为空进行判断,也可以写成p==NULL
    if (!p) {
        return false;
    }
//    开辟一块新空间,让s指针指向这个结点,这个结点为要插入的结点
    LNode *s = (LNode *) malloc(sizeof(LNode));
//    判断开辟的空间是否成功,考试的时候写的下的话最好也写上
    if (!s) {
        return overflow;
    }
    s->data = e;
//    s指向结点的指针域指向p指针指向结点的指针域,也就是p指向结点的下一个位置
    s->next = p->next;
//    让p指向结点的指针域指向s指向的结点,也就是让s成为p的下一个位置
    p->next = s;
//    以上的两个操作不可以换顺序
    return true;
}

// 6. 单链表指定结点的后插操作
Status List_InsertNextNode(LNode *p, ElemType e) {
    /*
     * 时间复杂度O(1)
     */
//    判断指定结点是否为空,这里也可以写成p==NULL
    if (!p) {
        return false;
    }
//    s指针指向了这个新开辟的空间,作为新插入的结点
    LNode *s = (LNode *) malloc(sizeof(LNode));
//    判断开辟的空间是否成功
    if (!s) {
        return overflow;
    }
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}

// 7. 单链表的指定结点前插入1
Status List_InsertPreNodeFirst(LinkList L, LNode *p, ElemType e) {
    /*
     * 时间复杂度O(n)
     * 算法思想:找到当前结点的前一个结点,相当于进行后插操作
     * 指定结点为第一个结点的处理:正常,找到前一个结点,也就是头结点
     * 指定结点为最后一个结点的处理:正常,找到尾结点的前驱,
     * 指定结点为最后一个结点后的NULL,正常,找到尾结点即可
     */
//    声明初始化q指针指向单链表的头结点,这里注意,指定结点p有可能是首元结点,它的前一个位置就是头结点,所以q要从头结点开始
    LNode *q = L;
//    找到p位置的前一个位置
//    如果q的下一个位置不是p,q往后移动一个位置,直到q为p的前驱
    while (q->next != p) {
        q = q->next;
    }
    LNode *s = (LNode *) malloc(sizeof(LNode));
    s->data = e;
    s->next = q->next;
    q->next = s;
    return true;
}

// 8. 单链表的结点前插入2
Status List_InsertPreNodeSecond(LNode *p, ElemType e) {
    /*
     * 时间复杂度O(1)
     * 算法思想:将指定结点后插入一个新节点,将指定结点的值放到新结点,要插入的值放到指定结点
     * 这里注意和第一种方法的区别
     * 指定结点为第一个结点的处理:正常按上述操作执行
     * 指定结点为尾结点的处理:正常处理
     * 指定结点为尾结点后的NULL的处理:无法处理(和第一个方法区别),这种方法的问题,或者处理的话就是找到尾结点,然后在尾结点后插入一个结点也行,这里就不处理了
     */
//    判断指定结点是否为空,这里也可以写成p!=NULL
    if (!p) {
        return false;
    }
//    s指针指向新节点作为p后插的结点
    LNode *s = (LNode *) malloc(sizeof(LNode));
//    这里注意s结点的值为指定结点p的值
    s->data = p->data;
//    指定结点p的值变为待插入的值e
    p->data = e;
//    s结点放在p后面
    s->next = p->next;
    p->next = s;
    return true;
}

// 9. 单链表的第i个结点删除
Status List_DeleteElem(LinkList L, int i, ElemType *e) {
    /*
     * 时间复杂度O(n)
     */
    /*
     *判断i的合法性,i的合法性范围[1,length]
     * 这里值判断最小是不是小于1,不判断最大的情况
     * 因为后面查找i-1的时候,如果太大查过了长度,会因为q为空不符合条件结束
     * 也可以最大值判断i>List_LengthFirst(L),这么写的话,后面q的空值判断就可以省略了,考试的话可以这么些,减少代码量
     * 因为这样的操作已经保证了i值的合法性,i-1必然是合法的,如果i-1是0的话,返回的是头结点
     */
//    判断i的合法性
    if (i < 1) {
        return false;
    }
//    获取i-1位置的结点
    LNode *p = List_GetElem(L, i - 1);
//    判断q指向的结点是否为空
    if (!p) {
        return false;
    }
    /*
     * 删除i位置结点
     * p指向的结点是i-1位置的结点,所以将q指向p下一个位置的结点
     * 删除操作即是让p的指针域,也就是下一个位置成为q的下一个位置
     * 最后释放q指向的结点
     *
     * 注:如果选择题中不需要释放结点的时候也可以这么写
     * p->next=p->next->next;
     * 因为这么写的话p原来的后继就找不到了,无法释放空间
     */
    LNode *q = p->next;
    p->next = q->next;
    *e = q->data;
    free(q);
    return true;
}

// 10. 单链表的指定结点的删除1
Status List_DeleteNodeFirst(LinkList L, LNode *p, ElemType *e) {
    /*
     * 时间复杂度O(n)
     * 算法思想:和插入一样,找到p指向结点的前驱,后面的操作和上面一样
     * 这里注意
     * 第一个结点和最后一个结点的处理正常,都是寻找前驱
     */
//    p指向的结点有可能是首元结点,所以要从头结点开始
    LNode *q = L;
//    如果q的下一个位置不是p,则移动q到下一个位置
    while (q->next != p) {
        q = q->next;
    }
//    删除操作,核心代码
    q->next = p->next;
    *e = p->data;
    free(p);
    return true;
}

// 11. 单链表的指定结点的删除2
Status List_DeleteNodeSecond(LNode *p, ElemType *e) {
    /*
     * 时间复杂度O(1)
     * 算法思想:将p指向的结点和后面一个结点的值交换,将问题转化为删除p结点后面的结点
     * 这里注意
     * 第一个结点的处理正常
     * 最后一个结点只能寻找前驱删除,否则无法处理,这个方法的弊端
     */
    /*
     *  判断p指向的结点的后一个位置是否为NULL,来判断是否为尾结点
     *  这里也可以写成p->next==NULL
     *  这里不进行尾结点的处理,因为函数没有传入指向单链表头结点的头指针,无法进行处理
     */
    if (!p->next) {
        return false;
    }
//    这里要提前给e赋值,后面的p指向的结点值会覆盖
    *e = p->data;
//    让q指向p的下一个位置
    LNode *q = p->next;
//    这里只把q的值赋值给p,因为q的结点要删除,值是多少也没有意义了,在写就是多余操作了
    p->data = q->data;
    p->next = q->next;
    free(q);
    return true;
}

// 12. 单链表指定结点的后一个结点删除
Status List_DeleteElemNextNode(LNode *p, ElemType *e) {
    /*
     * 时间复杂度O(1)
     */
    /*
     *  判断p结点是否为空,和p结点的下一个结点是否为空
     *  p结点为空,说明p为尾结点后的NULL,不合法
     *  p的后继为空,说明p是尾结点,不合法
     */
    if (!p || !p->next) {
        return false;
    }
    LNode *q = p->next;
    p->next = q->next;
    *e = q->data;
    free(q);
    return true;
}

// 13. 单链表的判空
Status List_IsEmpty(LinkList L) {
    /*
     * 时间复杂度O(1)
     */
    if (L->next == NULL) {
        return true;
    } else {
        return false;
    }
}

// 14. 单链表的长度
int List_LengthFirst(LinkList L) {
    /*
     * 时间复杂度O(n)
     */
    /*
     * 这里的p指针指向单链表的头结点的下一个结点,也就是首元结点
     */
    LNode *p = L->next;
    /*
     * 声明初始化计数器,用来计数单链表的结点个数
     * 这里初始化为0的原因是有可能单链表里面只有头结点的情况,这里没有对单链表是否为空进行判断,所以从0开始,把单链表为空的情况放进去
     */
    int count = 0;
/*
 * 这里的意义是如果p指针指向的结点不为空,则说明当前结点是单链表的一部分
 * 这里也可以写成p!=NULL
 */
    while (p) {
//        当前结点是单链表的一部分,计数器加1
        count++;
//        p指针移动到下一个位置
        p = p->next;
    }
//    count的值即为单链表的长度
    return count;
}

// 15. 单链表长度获取第二种方法
int List_LengthSecond(LinkList L) {
    /*
     * 时间复杂度O(n)
     */
//   单链表的长度有可能有空,所以从0开始,如果前面进行了判空,可以从1开始
    int count = 0;
//   p指针指向头结点的位置,这里区别第一种方法
    LNode *p = L;
/*
 * 这里通过判断p指向结点的下一个结点是否为空来计数
 * 和第一种方法进行区别
 * 这里的条件也可以写成p->next!=NULL
 */
    while (p->next) {
//        说明当前p指向的结点后面还有结点,移动p指针
        p = p->next;
//        计数器加1
        count++;
    }
    return count;
}

// 16. 单链表的查找---按位查找
LNode *List_GetElem(LinkList L, int i) {
    /*
     * 时间复杂度O(n)
     */
//    判断单链表是否为空,也可以不写,让后面while循环去判断,因为p为空,最后返回空值来处理,都可
//    这种写法在实际中更容易找到出错的原因,排bug
//    考试的话可以不写,减少非必需的代码,防止写不下
    if (List_IsEmpty(L)) {
//        说明单链表内没有元素
        return false;
    }
    /*
     * 声明并初始化单链表索引序号,标记单链表遍历到第几个,默认从1开始
     * 如果i值为0,返回头结点
     * 如果i值小于0,返回空值
     * 如果i值的超过了单链表的长度的话,p指针最终会指向尾结点后面的NULL,因为不满足p不为空的条件,循环终止,返回p指针,也就是空值
     * 如果i合法的情况下,如果查找到元素,会因为j=i不满足循环继续的条件而停止循环,返回p指针指向的结点,为查到的结点
     */
    if (i == 0) {
        return L;
    }
//    条件判断也可以写成i<0
    if (i < 1) {
//       也可以写成return false
        return NULL;
    }
    int j = 1;
/*
 *     p指针指向第一个结点,也就是首元结点,因为j是从1开始的,也就是首元结点开始遍历
 *     如果是从头结点开始,查到的结点为对应i的结点的前一个结点
 *     int j = 0;
 *     LNode *p = L;
 *     while(p->next&&j<i){
 *          p=p->next;
 *          j++;
 *     }
 */
    LNode *p = L->next;
//    注意条件判断
//  如果出现i的值大于单链表长度的情况,会因为p结点为空不符合条件而结束,返回空值,
//  也可以在前面加入对i是否大于单链表长度的判断,这样的话可以减少不合法位置的判断时间
//  这里没有写是因为可以减少代码量
    while (p && j < i) {
//        说明没有找到
//      p指针的位置移动一位
        p = p->next;
//        j的索引值加1
        j++;
    }
    return p;
}

// 17. 单链表的查找---按值查找
LNode *List_LocateElem(LinkList L, ElemType e) {
    /*
     * 时间复杂度O(n)
     */
//    判断单链表是否为空,也可以不写,让后面while循环去判断,因为p为空,最后返回空值来处理,都可
//  这种写法在实际中更容易找到出错的原因,排bug
//考试的话可以不写,减少非必需的代码,防止写不下
    if (List_IsEmpty(L)) {
        return false;
    }
    LNode *p = L->next;
    /*
     * 这里有两个判断条件
     * 1.   p 代表p不为空,也可以按书上的写法p!=NULL,效果是一样的,如果p为空,代表单链表内没有查询到和e值相等的结点,此时p指向NULL(尾结点后面的NULL),返回p(NULL)
     * 2.   p->data!=e,代表p指针指向的结点中的数据域的值不等于e,如果查询到p指向的结点的数据域和e相等的话,会因为不满足这个条件结束循环,返回p指向的结点
     *  注:这里的第二个条件也可以在while循环里写一个if语句,效果一样,只是这种写法,不如上面的写法精炼简洁,考试有可能会因为行数太多导致地方不够写,如下:
     *  while(p!=NULL){
     *      if(p->data==e){
     *          break;
     *      }
     *  }
     *  return p;
     */
    while (p && p->data != e) {
//        没有找到结点,移动p指针
        p = p->next;
    }
    return p;
}

// 18. 单链表的打印操作
Status List_PrintElem(LinkList L) {
    /*
     * 时间复杂度O(n)
     */
//    判断单链表是否为空
    if (List_IsEmpty(L)) {
        return false;
    }
//    声明一个指针p指向头结点的下一个位置,也就是首元结点
    LNode *p = L->next;
    printf("单链表的内容输出:");
//    判断当前结点是否为空,如果为空,终止循环
    while (p != NULL) {
        printf("%d ", p->data);
//        输出之后将p指针移动到下一个位置
        p = p->next;
    }
    printf("\n");
//    注:这里的p指针为静态分配的变量,不是动态分配,因此不需要通过free来释放空间,该变量会随着函数执行的完成而自动释放空间
    return true;
}

// 19. 两个单链表的合并1
LinkList List_MergeFirst(LinkList L1, LinkList L2, LinkList L3) {
    /*
     * 两个链表按从小到大合并(从大到小也一样)
     * 假设:L1长度m,L2长度n,m>n
     * 时间复杂度O(n)
     * 方法说明:将两个链表的结点都放在第三个链表中,这有个弊端,如果L1或者L2销毁了,那么L3也不能用了
     */
//    对两个链表进行判空操作,不能为空
    if (!L1 || !L2) {
        return false;
    }
//    p,q指针分别指向L1,L2的首元结点,r指针指向L3的头结点
    LNode *p = L1->next;
    LNode *q = L2->next;
    LNode *r = L3;
//    如果p或者q的结点为空,说明有一个链表已经没有了,直接把另外一个链表都放在这个链表的后面就好
    while (p && q) {
        /*
         * if p小于q的值,则r指向p,否则指向q
         * 然后移动r指针到那个结点
         * 最后移动p或者q到下一个位置
         */
        if (p->data <= q->data) {
            r->next = p;
            r = p;
            p = p->next;
        } else {
            r->next = q;
            r = q;
            q = q->next;
        }
    }
    /*
     * 这里说明
     * 循环结束后,如果有一个链表的结点没有了,则直接把另外一个链表的剩余结点都放进来
     * 这里要判断是哪个链表不为空,那个空
     */
    if (p) {
        r->next = p;
    }
    if (q) {
        r->next = q;
    }
//    这里也可以用这个写法,减少代码量,考试比较推荐,上面的比较清晰好理解
//    r->next = p ? p : q;
    return L3;
}

// 20. 两个单链表的合并2
LinkList List_MergeSecond(LinkList L1, LinkList L2) {
    /*
     * 两个链表按从小到大合并(从大到小也一样)
     * 假设:L1长度m,L2长度n,m>n
     * 时间复杂度O(n)
     * 方法说明:这里将L1的头结点作为了第三方的头结点,这么做的好处是节省空间,两个链表的结点都放在这里面,
     * 合并完成后会销毁L2的头结点,这个有个弊端,合并完成后,L1和L2就不存在了
     * 这种写法比较多,题目常考
     */
//    对两个链表进行判空操作,不能为空
    if (!L1 || !L2) {
        return false;
    }
//    p,q指针分别指向L1,L2的首元结点,r指针指向L1的头结点
    LNode *p = L1->next;
    LNode *q = L2->next;
    LNode *r = L1;
//    如果p或者q的结点为空,说明有一个链表已经没有了,直接把另外一个链表都放在这个链表的后面就好
    while (p && q) {
        /*
         * if p小于q的值,则r指向p,否则指向q
         * 然后移动r指针到那个结点
         * 最后移动p或者q到下一个位置
         */
        if (p->data <= q->data) {
            r->next = p;
            r = p;
            p = p->next;
        } else {
            r->next = q;
            r = q;
            q = q->next;
        }
    }
    /*
     * 这里说明
     * 循环结束后,如果有一个链表的结点没有了,则直接把另外一个链表的剩余结点都放进来
     * 这里要判断是哪个链表不为空,那个空
     */
    if (p) {
        r->next = p;
    }
    if (q) {
        r->next = q;
    }
//    这里也可以用这个写法,减少代码量,考试比较推荐
//    r->next = p ? p : q;
    free(L2);
    return L1;
}

// 21. 单链表的清空
Status List_Clear(LinkList L){
    /*
     * 时间复杂度O(n)
     * 清空就是把带头结点的单链表除了头结点以外的所有结点全部删除,销毁的话会把头结点也删除
     * 如果是不带头结点的单链表,清空和销毁其实没啥差别
     */
    if(List_IsEmpty(L)){
        return true;
    }
    LNode *p=L->next;
    while (L->next){
        L->next=L->next->next;
        free(p);
    }
    return true;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值