单链表

单链表

CONTENT

  链表介绍

   实现代码

   写在最后

一、链表介绍

  
  链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。

  单链表时一种在内存上节点物理空间并不连续的线性表,主要优势是空间利用率比较高。

二、实现代码

 

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;

/**********线性表---单链表**********/
// 说明:本代码主观性比较强,仅供参考,欢迎交流和学习
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASBLE -1
#define OVERFLOW -2

typedef int Status;
typedef int Elemtype;

// 定义链表的节点
typedef struct LNode
{
    Elemtype data;
    struct LNode *next;
} LNode, *LinkList;
// LNode可以代替 struct LNode
// *LinkList --> *LNode  --> struct LNode* 三者等价

// 基本操作:

// 逆位序输入n个元素,建立代表头节点的单链性表L
void CreatList_L(LinkList &L, int n);
// 取出链表中第i个节点中的信息
Status GetElem_L(LinkList L, int i, Elemtype &e);
// 在表头后第i个位置之前插入元素e
Status ListInsert_L(LinkList L, int i, Elemtype e);
// 删除链表的第i个元素并返回
Status ListDelet_L(LinkList L, int i, Elemtype &e);
// 打印链表
Status ListPrint_L(LinkList L);
// 合并两个有序的链表为一个链表
void MergeList_L(LinkList La, LinkList Lb, LinkList Lc);
// 清空链表
void ListClear_L(LinkList L);
// 获取链表长度
int ListLength_L(LinkList L);

int main()
{
    LinkList L;
    // LNode *L;  // 两者等效
    // 1 创建链表
    CreatList_L(L, 3);
    // 2 取出链表第2个元素
    Elemtype e;
    GetElem_L(L, 2, e);
    printf("链表第2个元素为:%d
", e);
    // 3 打印链表
    ListPrint_L(L);
    // 4 计算链表长度
    printf("L_Length = %d
", ListLength_L(L));
    // 5 插入元素
    ListInsert_L(L, 4, 4);
    printf("在第3个节点后插入元素后的链表为:
");
    ListPrint_L(L);
    // 6 删除节点
    ListDelet_L(L, 2, e);
    printf("节点已删除,删除的元素为:%d
", e);
    printf("链表剩余元素为:
");
    ListPrint_L(L);
    // 7 清空链表
    ListClear_L(L);
    printf("链表已清空
");
    return 0;
}

// 逆位序输入n个元素,建立代表头节点的单链性表L
// 头插法插入元素
void CreatList_L(LinkList &L, int n)
{
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    // 创建链表头,注意链表头也是一个结构体,但是表头里一般不储存数据
    // 将表头的指针初始化为NULL表示链表的长度为零,因为没有后继
    for (int i = n; i > 0; i--)
    {
        LinkList p = (LinkList)malloc(sizeof(LNode)); // 创建节点
        scanf("%d", &p->data);                        // 输入插入元素
        p->next = L->next, L->next = p;
        // 将p前继指向p后继的指针赋值给p使p->next指向p的后继
        // 同时也将p的前继的指针指向p
        // 实现 p前继->p->p后继
        // 同解如 ListInsert_L()最后
    }
}

// 取出链表中第i个节点中的信息
Status GetElem_L(LinkList L, int i, Elemtype &e)
{
    LinkList p = L->next;
    int j = 0;
    while (p && j < i - 1)
        p = p->next, j++; // 寻找在连表内的第i个元素
    if (!(p->next) || j > i)
        return ERROR; // 若链表长度小于i,返回错误
    e = p->data;      // 获取元素信息
    return OK;
}

// 在表头后第i个位置之后插入元素e
Status ListInsert_L(LinkList L, int i, Elemtype e)
{
    LinkList p = L;
    int j = 0;
    while (p && j < i - 1) // 当遇到尾节点时p == NULL 会结束函数
        p = p->next, j++;
    // p = p->next 作用:将等钱当前节点移动到下一个节点
    // j单纯用于计数,表示节点移动次数
    if (!p || j >= i)
        return ERROR;                                // 若移动到的节点是NULL或者超出链表长度就返回ERROR
    LinkList node = (LinkList)malloc(sizeof(LNode)); // 创建新的节点
    node->data = e;                                  // 给新的节点写入元素
    node->next = p->next;                            // KEY1
    p->next = node;                                  // KEY2
    // 插入前:p->p后   (p后是插入前p后的第一个元素)
    // 插入中:node->p后  KEY1
    //         p->node   KEY2
    // 插入后:p->node->p后
    // 同解如 CreatList_L()最后
    return OK;
}

// 删除链表的第i个元素并返回
Status ListDelet_L(LinkList L, int i, Elemtype &e)
{
    LinkList p = L;
    int j = 0;
    while (p->next && j < i - 1)
        p = p->next, j++;
    // 1. 寻找范围在链表内(while的条件中p->next限制只能在链表内找)
    // 2. 寻找第i个节点,并且让p指向它的前趋
    // 3. 为什么要让p指向前驱?
    //  删除的原理是,让第i个节点的前驱的指针直接指向第i个节点的下一个节点
    if (!(p->next) || j >= i)
        return ERROR;  // 若未找到元素将返回错误
    LinkList q;        // 定义一个临时的指针,指向节点i
    q = p->next;       // KEY1
    p->next = q->next; // KEY2
    e = q->data;       // 写入数据
    // KEY1: p->next指向节点i,所q->next也会指向节点i
    // KEY2:获取节点i的后继的节点地址赋值给q->next会使节点i的前驱与后继连接
    // 删除前 p前驱->p->p后继
    // 删除后 p前驱->p后继
    free(q); // 释放q防止内存积压
    return OK;
}

// 打印链表
Status ListPrint_L(LinkList L)
{
    LinkList pMove = L->next; // 定义一个指针分别去找链表中的每一个元素
    if (!(L->next))
    {
        printf("NULL
");
        return ERROR;
    }
    while (pMove)
    {
        printf("%d ", pMove->data); // 输出的格式根据元素的不同更换
        pMove = pMove->next;        // 移动到下一个节点
    }
    putchar('
');
    return OK;
}

// 合并两个有序的链表为一个链表(使用前提La与Lb为有序表)
void MergeList_L(LinkList La, LinkList Lb, LinkList Lc)
{
    LinkList pa, pb, pc;          // 创建三个指针,以读取链表
    pa = La->next, pb = Lb->next; // 初始化La和Lb的指针
    Lc = pc = La;                 // 以La为表头
    while (pa && pb)
        if (pa->data <= pb->data)
            pc->next = pa, pc = pa, pa = pa->next; // pc是Lc的指针,不断为Lc连接新的节点,然后使pc后移到新的节点,最后又将pa移动到La的下一个节点
        else
            pc->next = pb, pc = pb, pb = pb->next; // 同理
    pc->next = pa ? pa : pc;
    // 插入链表剩余的部分,如果pa是空说明pc不空,就把pc的后半段插入Lc,否则pb的后半段插入Lc
    free(Lb); //节点Lb已经没有作用了,释放内存
}

// 清空链表
void ListClear_L(LinkList L)
{
    LinkList pClr, p;
    p = pClr = L->next;
    // p是移动指针不断移动到下一个节点,pClr应释放的节点
    while (p)
    {
        p = p->next;
        free(pClr);
        pClr = p;
    }
    free(pClr);
    L->next = NULL;
    return;
}

// 获取链表长度
int ListLength_L(LinkList L)
{
    LinkList p = L->next;
    int count = 0;
    while (p)
        p = p->next, count++; // 节点不为空时,执行循环并计次
    return count;
}

 

三、写在最后

  代码个人观点比较强,若代码中存在bug欢迎各位reader指出。

 

原文地址:https://www.cnblogs.com/kirk-notes/p/14550993.html