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

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

文章目录

  • 1.基本操作
    • 1.1 结构体定义
    • 1.2 初始化
    • 1.3 判空
    • 1.4 按位序插入
    • 1.5 指定结点后插操作
    • 1.6 指定结点前插操作_1
    • 1.7 指定结点前插操作_2
    • 1.8 按位序删除
    • 1.9 按位查找
    • 1.10 按值查找
    • 1.11 表的长度
    • 1.12 单链表建立(尾插法)
    • 1.13 单链表建立(头插法)
    • 1.14 输出所有链表元素
  • 2.完整代码
  • 3.运行截图

各部分的解释已经以注释形式写于代码中。
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 = https://www.it610.com/article/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 = https://www.it610.com/article/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 = https://www.it610.com/article/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 = https://www.it610.com/article/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 = https://www.it610.com/article/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 = https://www.it610.com/article/p->data; p->data =https://www.it610.com/article/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->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; }

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 = https://www.it610.com/article/x; r = L; L->next = NULL; // 输入结点的值 scanf("%d", &x); // 表示键入9999后建表结束 while(x != 9999) { s = (LNode*)malloc(sizeof(LNode)); s->data = https://www.it610.com/article/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 = https://www.it610.com/article/x; L->next = NULL; // 输入结点的值 scanf("%d", &x); // 表示键入9999后建表结束 while(x != 9999) { s = (LNode*)malloc(sizeof(LNode)); s->data = https://www.it610.com/article/x; s->next = L->next; // 更新头指针 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 #include/** * 注意:对于不带头结点的情况,一定要考虑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 = https://www.it610.com/article/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 = https://www.it610.com/article/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 = https://www.it610.com/article/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 = https://www.it610.com/article/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 = https://www.it610.com/article/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 = https://www.it610.com/article/p->data; p->data =https://www.it610.com/article/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 = https://www.it610.com/article/x; r = L; L->next = NULL; // 输入结点的值 scanf("%d", &x); // 表示键入9999后建表结束 while(x != 9999) { s = (LNode*)malloc(sizeof(LNode)); s->data = https://www.it610.com/article/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 = https://www.it610.com/article/x; L->next = NULL; // 输入结点的值 scanf("%d", &x); // 表示键入9999后建表结束 while(x != 9999) { s = (LNode*)malloc(sizeof(LNode)); s->data = https://www.it610.com/article/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.运行截图 数据结构|【数据结构】【王道】【线性表】无头结点单链表的实现及基本操作(可直接运行)
文章图片

    推荐阅读