【数据结构】【王道】【线性表】无头结点单链表的实现及基本操作(可直接运行)

总目录


各部分的解释已经以注释形式写于代码中。

1.基本操作

1.1 结构体定义

// 不带头结点
typedef struct LNode {  // 定义单链表结点类型
    int data;   // 数据域
    struct LNode *next; // 指针域
} LNode, *LinkList;

1.2 初始化

//初始化一个空的单链表
bool InitList(LinkList &L){
    L = NULL;   //空表,暂时还没有任何结点
    return true;
}

1.3 判空

// 判断单链表是否为空
bool Empty(LinkList L) {
    // 无头结点,那么头指针指向NULL,单链表即为空
    if(L == NULL) {
        return true;
    } else {
        return false;
    }
    // 简洁
    // return  (L == NULL);
}

1.4 按位序插入

// 按位序插入
// 在第i个位置插入元素e
bool ListInsert(LinkList &L, int i, int e) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    // 判断插入位序是否存在问题
    if(L == NULL || i < 1) {
        return NULL;
    }
    // 判断插入的位置是否是第1个
    // 由于没有头指针,所以此处操作与其它结点不同,需要单独处理,以更新头指针
    if(i == 1) {
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = e;
        // 令新结点指向之前头指针所指的结点
        s->next = L;
        // 头指针指向新结点
        L = s;
        return true;
    }
    LNode *p;   // 指针p指向当前扫描到的结点
    int j = 1;  // 当前p指向的是第几个结点
    p = L;      // 初始令p指向第一个结点
    // 通过循环,找到第i-1个结点
    // 如果i大于链表长度,则不可插入,此时p指向最后结点的指针域,即NULL
    while(p != NULL && j < i - 1){
        p = p->next;
        j++;
    }
    // p值为NULL说明遍历完成后仍未为找到位序为i的结点,说明i大于链表长度,i不合法
    if (p == NULL)       
        return false;
    // 在第i-1个结点后插入新结点
    LNode *s = (LNode *)malloc(sizeof(LNode));
    // 注意顺序,应该先让要插入结点的指针域指向后一结点
    // 之后再断开前一结点与后一结点的指针,使前一结点指向要插入结点
    s->data = e; 
    s->next = p->next;
    //将结点s连到p后
    p->next = s;
    return true;
}

1.5 指定结点后插操作

// 指定结点后插操作:在p结点之后插入元素e
// 此操作与带头结点情况相同,由于是指定结点后插,所以即使是第一个结点也无需特殊处理
// LNode强调为结点
bool InsertNextNode(LNode *p, int e) {
    // p不合理的情况
    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;
}

1.6 指定结点前插操作_1

// 指定结点前插操作:在p结点之前
// 此处需要传入头指针,因为是在p前插入
// 而由于此为单链表,如果只知道p节点,只能遍历找到p后的元素,而找不到p前的元素
// 所以只能通过头结点遍历,找到p的前驱结点
bool InsertPriorNodeRoutine(LinkList L, LNode *p, int e) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    // p不合理的情况
    if(L == NULL || p == NULL) {
        return false;
    }
    LNode *s = (LNode *)malloc(sizeof(LNode));
    // 内存分配失败的情况
    if(s == NULL) {
        return false;
    }
    // 插入时是第一个结点的情况,需要特殊处理,以更新头指针
    if(L == p) {
        s->data = e;
        s->next = p;
        L = s;
        return true;
    }
    // 定义一个指针,初始指向第一个结点
    LNode *p1 = L;
    // 遍历找到p结点的前驱结点
    while(p1 != NULL && p1->next != p) {
        p1 = p1->next;
    }
    // p1若等于NULL,表示p不存在或是链表无元素
    if(p1 == NULL) {
        return false;
    }
    // 插入操作
    s->data = e;
    s->next = p;
    p1->next = s;
    return true;
}

1.7 指定结点前插操作_2

// 指定结点前插操作:在p结点之前
// 此处可以通过一种特殊方法完成前插
// 本质上还是后插操作
bool InsertPriorNode(LNode *p, int e) {
    // p不合理的情况
    if(p == NULL) {
        return false;
    }
    LNode *s = (LNode *)malloc(sizeof(LNode));
    // 内存分配失败的情况
    if(s == NULL) {
        return false;
    }
    // 插入操作
    // 通过将s插入p结点之后,然后将s结点与p结点的数据域进行互换
    // 可以视作完成了前插操作
    s->next = p->next;
    p->next = s;
    s->data = p->data;
    p->data =e;
    return true;
}

1.8 按位序删除

// 按位序删除
bool ListDelete(LinkList &L, int i, int &e) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    // i不合理的情况
    if(L == NULL || i < 1) {
        return false;
    }
    // 删除第一个结点的情况
    // 需要更新头指针,要单独处理
    if(i == 1) {
        LNode* q = L;
        e = q->data;
        // 一般情况为p->next = q->next
        // 但由于无头结点,所以需要直接令L指向q->next,以此更新头指针
        L = q->next;
        free(q);
        return true;
    }
    LNode *p;   // 指针p指向当前扫描到的结点
    int j = 1;  // 当前p指向的是第几个结点
    // 初始令p指向头指针
    p = L;
    // 通过遍历找到第i - 1个结点,即要删除结点的前驱结点
    while(p != NULL && j < i - 1) {
        p = p->next;
        j++;
    }
    // 若p等于NULL,则要么此链表为空,要么i值大于链表长度,显然不合理
    // p->next为空表示第i-1个结点之后已经没有其他结点了,无法进行删除操作
    if(p == NULL || p->next == NULL) {
        return false;
    }
    // 删除操作
    // 就是将前驱结点指针域指向后继结点
    LNode *q = p->next;    
    e = q->data;
    p->next = q->next;
    // 不要忘记释放空间!!!
    free(q); 
    return true;
}

1.9 按位查找

// 按位查找,返回第i个元素
// 之前操作部分按位查找部分可以以此部分代码代替,提高代码复用性(封装)
LNode* GetElem(LinkList L, int i) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    if(L == NULL) {
        return NULL;
    }
    // 判断插入位序是否存在问题
    // 由于此是不带头结点的情况,所以i值不能小于1,因为第一个结点前是头指针
    // 带头结点的第一个结点前是头结点
    if(i < 1)
        return NULL;
    LNode *p;   // 指针p指向当前扫描到的结点
    int j = 1;  // 当前p指向的是第几个结点
    p = L;      // 初始令p指向第一个结点
    // 通过循环,找到第i个结点
    // 如果i大于链表长度,则找不到此节点,此时p指向最后结点的指针域,即NULL
    // 如果i初始为1,会直接返回第一个结点结点地址
    while(p != NULL && j < i) {
        p = p->next;
        j++;
    }
    return p;
}

1.10 按值查找

// 按值查找,找到数据域等于e的结点
LNode* LocateElem(LinkList L, int e) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    if(L == NULL) {
        return NULL;
    }
    LNode* p = L;
    // 从第一个结点开始查找数据域为e的结点
    // 找不到,则会在遍历后指向NULL
    while (p != NULL && p->data != e) {
        p = p->next;
    }
    return p;
}

1.11 表的长度

// 求表的长度
// 就是通过遍历所有结点,然后通过计数器来累加得到长度
int Length(LinkList L) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    if(L == NULL) {
        return 0;
    }
    int len = 0;
    LNode* p = L;
    while(p->next != NULL) {
        p = p->next;
        len++;
    }
    return len;
}

1.12 单链表建立(尾插法)

// 尾插法
// 通过尾插法建立的链表,顺序为插入时元素的顺序
LinkList TailInsert(LinkList &L) {
    // 设置ElemType为整型
    int x;
    // 定义表尾指针
    // 对于尾插法,如果只通过头结点遍历的话
    // 每次插入时都要先遍历到表尾,时间复杂度为O(1+2+...+n-1)=O(n^2)
    // 所以选择增加一个尾指针,来避免遍历
    // s为要插入的结点,r为表尾指针
    LNode *s, *r = L;
    // 输入结点的值
    scanf("%d", &x);
    // 第一个结点需要特殊处理
    L = (LNode*)malloc(sizeof(LNode));
    L->data = x;
    r = L;
    L->next = NULL;
    // 输入结点的值
    scanf("%d", &x);
    // 表示键入9999后建表结束
    while(x != 9999) {
        s = (LNode*)malloc(sizeof(LNode));
        s->data = x;
        r->next = s;
        // r指向新的表尾结点
        r = s;
        // 继续输入下一个结点的值,直至输入9999结束
        scanf("%d", &x);
    }
    // 尾指针置空
    r->next = NULL;
    return L;
}

1.13 单链表建立(头插法)

// 头插法
// 通过头插法建立的链表,顺序为插入时元素的逆序
LinkList HeadInsert(LinkList &L) {
    // 设置ElemType为整型
    int x;
    // 不同于尾插法,头插法无需定义更多指针
    LNode* s;
    // 输入结点的值
    scanf("%d", &x);
    // 第一个结点需要特殊处理
    L = (LNode*)malloc(sizeof(LNode));
    L->data = x;
    L->next = NULL;
    // 输入结点的值
    scanf("%d", &x);
    // 表示键入9999后建表结束
    while(x != 9999) {
        s = (LNode*)malloc(sizeof(LNode));
        s->data = x;
        s->next = L;
        // 更新头指针
        L = s;
        // 继续输入下一个结点的值,直至输入9999结束
        scanf("%d", &x);
    }
    return L;
}

1.14 输出所有链表元素

// 输出所有链表元素
void PrintList(LinkList L) {
    if(Empty(L)) {
        printf("the list is empty");
    }
    LNode* p = L;
    while(p !=  NULL) {
        printf("%d  ", p->data);
        p = p->next;
    }
    printf("\n");
}

2.完整代码

#include<stdio.h>
#include<stdlib.h>

/**
 * 注意:对于不带头结点的情况,一定要考虑L为NULL的可能性
 * 因为在没有头结点的情况下,头指针为NULL是不能够取数据域或者指针域的
 */

// 不带头结点
typedef struct LNode {  // 定义单链表结点类型
    int data;   // 数据域
    struct LNode *next; // 指针域
} LNode, *LinkList;

//初始化一个空的单链表
bool InitList(LinkList &L){
    L = NULL;   //空表,暂时还没有任何结点
    return true;
}

// 判断单链表是否为空
bool Empty(LinkList L) {
    // 无头结点,那么头指针指向NULL,单链表即为空
    if(L == NULL) {
        return true;
    } else {
        return false;
    }
    // 简洁
    // return  (L == NULL);
}

// 按位序插入
// 在第i个位置插入元素e
bool ListInsert(LinkList &L, int i, int e) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    // 判断插入位序是否存在问题
    if(L == NULL || i < 1) {
        return NULL;
    }
    // 判断插入的位置是否是第1个
    // 由于没有头指针,所以此处操作与其它结点不同,需要单独处理,以更新头指针
    if(i == 1) {
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = e;
        // 令新结点指向之前头指针所指的结点
        s->next = L;
        // 头指针指向新结点
        L = s;
        return true;
    }
    LNode *p;   // 指针p指向当前扫描到的结点
    int j = 1;  // 当前p指向的是第几个结点
    p = L;      // 初始令p指向第一个结点
    // 通过循环,找到第i-1个结点
    // 如果i大于链表长度,则不可插入,此时p指向最后结点的指针域,即NULL
    while(p != NULL && j < i - 1){
        p = p->next;
        j++;
    }
    // p值为NULL说明遍历完成后仍未为找到位序为i的结点,说明i大于链表长度,i不合法
    if (p == NULL)       
        return false;
    // 在第i-1个结点后插入新结点
    LNode *s = (LNode *)malloc(sizeof(LNode));
    // 注意顺序,应该先让要插入结点的指针域指向后一结点
    // 之后再断开前一结点与后一结点的指针,使前一结点指向要插入结点
    s->data = e; 
    s->next = p->next;
    //将结点s连到p后
    p->next = s;
    return true;
}

// 指定结点后插操作:在p结点之后插入元素e
// 此操作与带头结点情况相同,由于是指定结点后插,所以即使是第一个结点也无需特殊处理
// LNode强调为结点
bool InsertNextNode(LNode *p, int e) {
    // p不合理的情况
    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结点之前
// 此处需要传入头指针,因为是在p前插入
// 而由于此为单链表,如果只知道p节点,只能遍历找到p后的元素,而找不到p前的元素
// 所以只能通过头结点遍历,找到p的前驱结点
bool InsertPriorNodeRoutine(LinkList L, LNode *p, int e) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    // p不合理的情况
    if(L == NULL || p == NULL) {
        return false;
    }
    LNode *s = (LNode *)malloc(sizeof(LNode));
    // 内存分配失败的情况
    if(s == NULL) {
        return false;
    }
    // 插入时是第一个结点的情况,需要特殊处理,以更新头指针
    if(L == p) {
        s->data = e;
        s->next = p;
        L = s;
        return true;
    }
    // 定义一个指针,初始指向第一个结点
    LNode *p1 = L;
    // 遍历找到p结点的前驱结点
    while(p1 != NULL && p1->next != p) {
        p1 = p1->next;
    }
    // p1若等于NULL,表示p不存在或是链表无元素
    if(p1 == NULL) {
        return false;
    }
    // 插入操作
    s->data = e;
    s->next = p;
    p1->next = s;
    return true;
}

// 指定结点前插操作:在p结点之前
// 此处可以通过一种特殊方法完成前插
// 本质上还是后插操作
bool InsertPriorNode(LNode *p, int e) {
    // p不合理的情况
    if(p == NULL) {
        return false;
    }
    LNode *s = (LNode *)malloc(sizeof(LNode));
    // 内存分配失败的情况
    if(s == NULL) {
        return false;
    }
    // 插入操作
    // 通过将s插入p结点之后,然后将s结点与p结点的数据域进行互换
    // 可以视作完成了前插操作
    s->next = p->next;
    p->next = s;
    s->data = p->data;
    p->data =e;
    return true;
}

// 按位序删除
bool ListDelete(LinkList &L, int i, int &e) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    // i不合理的情况
    if(L == NULL || i < 1) {
        return false;
    }
    // 删除第一个结点的情况
    // 需要更新头指针,要单独处理
    if(i == 1) {
        LNode* q = L->next;
        e = q->data;
        // 一般情况为p->next = q->next
        // 但由于无头结点,所以需要直接令L指向q->next,以此更新头指针
        L = q->next;
        free(q);
        return true;
    }
    LNode *p;   // 指针p指向当前扫描到的结点
    int j = 1;  // 当前p指向的是第几个结点
    // 初始令p指向头指针
    p = L;
    // 通过遍历找到第i - 1个结点,即要删除结点的前驱结点
    while(p != NULL && j < i - 1) {
        p = p->next;
        j++;
    }
    // 若p等于NULL,则要么此链表为空,要么i值大于链表长度,显然不合理
    // p->next为空表示第i-1个结点之后已经没有其他结点了,无法进行删除操作
    if(p == NULL || p->next == NULL) {
        return false;
    }
    // 删除操作
    // 就是将前驱结点指针域指向后继结点
    LNode *q = p->next;    
    e = q->data;
    p->next = q->next;
    // 不要忘记释放空间!!!
    free(q); 
    return true;
}

// 按位查找,返回第i个元素
// 之前操作部分按位查找部分可以以此部分代码代替,提高代码复用性(封装)
LNode* GetElem(LinkList L, int i) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    if(L == NULL) {
        return NULL;
    }
    // 判断插入位序是否存在问题
    // 由于此是不带头结点的情况,所以i值不能小于1,因为第一个结点前是头指针
    // 带头结点的第一个结点前是头结点
    if(i < 1)
        return NULL;
    LNode *p;   // 指针p指向当前扫描到的结点
    int j = 1;  // 当前p指向的是第几个结点
    p = L;      // 初始令p指向第一个结点
    // 通过循环,找到第i个结点
    // 如果i大于链表长度,则找不到此节点,此时p指向最后结点的指针域,即NULL
    // 如果i初始为1,会直接返回第一个结点结点地址
    while(p != NULL && j < i) {
        p = p->next;
        j++;
    }
    return p;
}

// 按值查找,找到数据域等于e的结点
LNode* LocateElem(LinkList L, int e) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    if(L == NULL) {
        return NULL;
    }
    LNode* p = L;
    // 从第一个结点开始查找数据域为e的结点
    // 找不到,则会在遍历后指向NULL
    while (p != NULL && p->data != e) {
        p = p->next;
    }
    return p;
}

// 求表的长度
// 就是通过遍历所有结点,然后通过计数器来累加得到长度
int Length(LinkList L) {
    // 链表为空时的情况
    // 若为空,则取指针域或数据域会报错
    if(L == NULL) {
        return 0;
    }
    int len = 0;
    LNode* p = L;
    while(p->next != NULL) {
        p = p->next;
        len++;
    }
    return len;
}

// 尾插法
// 通过尾插法建立的链表,顺序为插入时元素的顺序
LinkList TailInsert(LinkList &L) {
    // 设置ElemType为整型
    int x;
    // 定义表尾指针
    // 对于尾插法,如果只通过头结点遍历的话
    // 每次插入时都要先遍历到表尾,时间复杂度为O(1+2+...+n-1)=O(n^2)
    // 所以选择增加一个尾指针,来避免遍历
    // s为要插入的结点,r为表尾指针
    LNode *s, *r = L;
    // 输入结点的值
    scanf("%d", &x);
    // 第一个结点需要特殊处理
    L = (LNode*)malloc(sizeof(LNode));
    L->data = x;
    r = L;
    L->next = NULL;
    // 输入结点的值
    scanf("%d", &x);
    // 表示键入9999后建表结束
    while(x != 9999) {
        s = (LNode*)malloc(sizeof(LNode));
        s->data = x;
        r->next = s;
        // r指向新的表尾结点
        r = s;
        // 继续输入下一个结点的值,直至输入9999结束
        scanf("%d", &x);
    }
    // 尾指针置空
    r->next = NULL;
    return L;
}

// 头插法
// 通过头插法建立的链表,顺序为插入时元素的逆序
LinkList HeadInsert(LinkList &L) {
    // 设置ElemType为整型
    int x;
    // 不同于尾插法,头插法无需定义更多指针
    LNode* s;
    // 输入结点的值
    scanf("%d", &x);
    // 第一个结点需要特殊处理
    L = (LNode*)malloc(sizeof(LNode));
    L->data = x;
    L->next = NULL;
    // 输入结点的值
    scanf("%d", &x);
    // 表示键入9999后建表结束
    while(x != 9999) {
        s = (LNode*)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;
        // 更新头指针
        L = s;
        // 继续输入下一个结点的值,直至输入9999结束
        scanf("%d", &x);
    }
    return L;
}

// 输出所有链表元素
void PrintList(LinkList L) {
    if(Empty(L)) {
        printf("the list is empty");
    }
    LNode* p = L;
    while(p !=  NULL) {
        printf("%d  ", p->data);
        p = p->next;
    }
    printf("\n");
}

int main() {
    LinkList L;
    InitList(L);
    TailInsert(L);
    PrintList(L);
    printf("get_2:%d\n",  GetElem(L, 2)->data);
    printf("locate_5:%d\n",  LocateElem(L, 5)->data);
    printf("length:%d\n", Length(L));
    InsertPriorNode(GetElem(L, 2), 7);
    PrintList(L);
    InsertNextNode(GetElem(L, 2), 5);
    PrintList(L);
    ListInsert(L, 3, 3);
    PrintList(L);
    int e;
    ListDelete(L, 3, e);
    PrintList(L);
    printf("e:%d\n", e);
}

3.运行截图

在这里插入图片描述

  • 19
    点赞
  • 105
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Silver Star

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值