zoukankan      html  css  js  c++  java
  • 数据结构学习笔记2---链表

    数据结构学习笔记2---链表

    链表的概念

    什么是链表

    链表是一种用于存储数据集合的数据结构。 和顺序表共同组成线性表。

    链表的特点

    • 相邻元素之间通过指针进行连接
    • 最后一个元素的后继指针值为NULL
    • 在程序执行的过程中,链表的长度可以增加或缩小
    • 链表的空间能够按需分配(直到系统内存耗尽)
    • 没有内存空间的浪费(但是链表中的指针需要一些额外的内存开销)

    mark

    几种常见的链表

    • 单链表
    • 双向链表
    • 循环链表

    链表与数组的区别

    mark

    1. 对于数组来说,它需要一块连续的内存存储空间来存储,对内存的要求比较高,也就是说,如果我申请100M大小的数组的话,当内存中没有连续的、足够大的存储空间的时候,即便内存中剩余总可用空间大于100M,此时仍然会申请失败
    2. 对于链表来说,它恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用,所以,如果我们申请的是100M大小的链表,当内存中剩余可用空间大于100M的时候,无论是否连续,申请都不会有问题。

    单链表的数据结构

    //================线性表的单链表存储结构=================
    typedef struct LNode {
    ElemType data;//数据域
    struct LNode *next;//指针域
    }LNode,*LinkList;
    

    注:为了提高程序的可读性,在此对同一结构体指针类型起了两个名称:LNode,*LinkList,两者本质上是等价的。

    *通常习惯上用 LinkList定义单链表,强调定义的是某个单链表的头指针 ;用 LNode 定义指向单链表中任意结点的指针变量。

    头节点

    一般情况下,为了处理方便,在单链表的第一个结点之前附设一个结点,称之为头结点。

    mark

    首元结点、头结点、头指针三个容易混淆的概念 :

    mark

    单链表

    单链表是最重要的一种链表,也是其他链表的基础,接下来作重点介绍。

    单链表初始化

    算法步骤 :

    1. 生成新结点作为头结点,用头指针L 指向头结点。
    2. 头结点的指针域置空。

    算法描述 :

    Status InitList(LinkList *&L){
        L=new LNode;
        L->data=data;  
        L-next=NULL;
        return OK;
    }
    

    取值

    算法步骤 :

    1. 用指针p指向首元结点,用j做计数器初值赋为1。

    2. 从首元结点开始依次顺着链域 next 向下访问,只要指向当前结点的指针 p 不为空 (NULL), 并且没有到达序号为i的结点,则循环执行以下操作:

      p指向下一个结点;

      • 计数器j就相应加1。

    3. 退出循环时, 如果指针p为空, 或者计数器j大于i, 说明指定的序号i值不合法(i大于

      表长n或i小于等于0), 取值失败返回ERROR; 否则取值成功, 此时j=i时,p所指的结点就

      是要找的第i个结点,用参数e保存当前结点的数据域, 返回OK。

    算法描述 :

    Status GetElem(iLinkList L,int i,ElemType &e){
        p=L->next;
        j=1;
        while(p!=NULL&&j<i){
            p=p->next;
            j++;    
        }
        
        if (!p||j>i){   //!p说明查询范围过大,j>i对应i<0的情况
            return ERROR;
        }
        e=p->data;
        return OK;    
    }
    

    单链表取值算法的平均时间复杂度为O(n)

    查找

    算法步骤 :

    1. 用指针p指向首元结点
    2. 从首元结点开始依次顺着链域next向下查找, 只要指向当前结点的指针p不为空, 并且p所指结点的数据域不等千给定值e, 则循环执行以下操作: p指向下一个结点 。
    3. 返回p。若查找成功,p此时即为结点的地址值,若查找失败,p的值即为NULL。

    算法描述 :

    LNode *LocateELem(LinkList L, Elemtype e){
        //在带头结点的单链表L中查找值为e的元素
        p=l->next;
        while(p&&p->data!=e){
            p=p->next;
        }
    	return p;
    }
    

    插入

    mark

    核心语句:s->next=p->next; p->next=s;

    算法描述 :

    Status Listinsert(LinkList &L,int i,ElemType e){
        //在单链表L中的第i个位置插入元素e
        //1.创建一个新节点
        s=new LNode;
        //2.找到第i-1的节点的位置
        p=L;
        j=0;
        //注:这里的注释条件和前开你查找是一样了,虽然p节点和j的指向仍然一致,但他们的七点整体前移了1,这是因为要用做后面的异常判断。
        while(p&&j<i-1){  
            p=p->next;
            j++;
        }
        
        if (!p||j>i-1) return ERROR; ///i>n+l或者i<1
        s-data=e;
        s->next=p->next;
        p->next=s;
        
        return OK;
    }
    

    删除

    mark

    核心语句:s=p->next; p->next=p->next->next; free(s);

    算法描述 :

    Status ListDelete(LinkList &L,int i){
        //删除链表L第i个节点
        
        //1. 找到第i-1个节点
        p=L;
        j=0;
        while(p->next&&j<i-1){  
            p=p->next;
            j++;
        }
        
        if(!(p->next)||j>i-1) return ERROR;  //当i>n或i<1时,删除位置不合理
        s=p->next; 
        p->next=p->next->next;
        free(s);
    
        return OK;
    }
    

    创建单链表

    前插法

    mark

    算法步骤 :

    1. 创建一个只有头结点的空链表。
    2. 根据待创建链表包括的元素个数n, 循环n次执行以下操作:
    • 生成一个新结点*p;

    • 输入元素值赋给新结点*p的数据域;

    • 将新结点*p插入到头结点之后。

    算法描述 :

    void CreateList_H(LinkList &L,int n){
        L=new LNode;
        L->next=NULL;   //先建立一个带头结点的空链表
        for(i=0;i<n;i++){
            p=new LNode;
            cin>>p->data;
            p->next=L->next;
            L->next=p;
    	}  
    }
    

    时间复杂度亦为O(n)

    尾插法

    mark

    算法步骤 :

    1. 创建一个只有头结点的空链表。
    2. 尾指针r初始化, 指向头结点。
    3. 根据创建链表包括的元素个数n, 循环n次执行以下操作:
    • 生成一个新结点*p;
    • 输入元素值赋给新结点*p 的数据域;
    • 将新结点p 插入到尾结点r之后;
    • 尾指针r指向新的尾结点*p。

    算法描述 :

    void CreateList_R(LinkList &L,int n){
        L=new LNode;
        L->next=NULL;
        r=L;  //尾指针r指向头结点
        for(i=0;i<n;i++){
            p=new LNode;
            cin>>p->data;
           p->next=NULL; 
            r->next=p;
            r=p;
    	}      
    }
    

    时间复杂度亦为O(n)

    c语言实现单链表

    #include <stdio.h>
    #include <stdlib.h>
    typedef struct Link {
        int  elem;
        struct Link *next;
    }link;
    link * initLink();
    //链表插入的函数,p是链表,elem是插入的结点的数据域,add是插入的位置
    link * insertElem(link * p, int elem, int add);
    //删除结点的函数,p代表操作链表,add代表删除节点的位置
    link * delElem(link * p, int add);
    //查找结点的函数,elem为目标结点的数据域的值
    int selectElem(link * p, int elem);
    //更新结点的函数,newElem为新的数据域的值
    link *amendElem(link * p, int add, int newElem);
    void display(link *p);
    int main() {
        //初始化链表(1,2,3,4)
        printf("初始化链表为:
    ");
        link *p = initLink();
        display(p);
        printf("在第4的位置插入元素5:
    ");
        p = insertElem(p, 5, 4);
        display(p);
        printf("删除元素3:
    ");
        p = delElem(p, 3);
        display(p);
        printf("查找元素2的位置为:
    ");
        int address = selectElem(p, 2);
        if (address == -1) {
            printf("没有该元素");
        }
        else {
            printf("元素2的位置为:%d
    ", address);
        }
        printf("更改第3的位置上的数据为7:
    ");
        p = amendElem(p, 3, 7);
        display(p);
        return 0;
    }
    link * initLink() {
        link * p = (link*)malloc(sizeof(link));//创建一个头结点
        link * temp = p;//声明一个指针指向头结点,用于遍历链表
        //生成链表
        for (int i = 1; i < 5; i++) {
            link *a = (link*)malloc(sizeof(link));
            a->elem = i;
            a->next = NULL;
            temp->next = a;
            temp = temp->next;
        }
        return p;
    }
    link * insertElem(link * p, int elem, int add) {
        link * temp = p;//创建临时结点temp
        //首先找到要插入位置的上一个结点
        for (int i = 1; i < add; i++) {
            temp = temp->next;
            if (temp == NULL) {
                printf("插入位置无效
    ");
                return p;
            }
        }
        //创建插入结点c
        link * c = (link*)malloc(sizeof(link));
        c->elem = elem;
        //向链表中插入结点
        c->next = temp->next;
        temp->next = c;
        return  p;
    }
    link * delElem(link * p, int add) {
        link * temp = p;
        //遍历到被删除结点的上一个结点
        for (int i = 1; i < add; i++) {
            temp = temp->next;
            if (temp->next == NULL) {
                printf("没有该结点
    ");
                return p;
            }
        }
        link * del = temp->next;//单独设置一个指针指向被删除结点,以防丢失
        temp->next = temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域
        free(del);//手动释放该结点,防止内存泄漏
        return p;
    }
    int selectElem(link * p, int elem) {
        link * t = p;
        int i = 1;
        while (t->next) {
            t = t->next;
            if (t->elem == elem) {
                return i;
            }
            i++;
        }
        return -1;
    }
    link *amendElem(link * p, int add, int newElem) {
        link * temp = p;
        temp = temp->next;//tamp指向首元结点
        //temp指向被删除结点
        for (int i = 1; i < add; i++) {
            temp = temp->next;
        }
        temp->elem = newElem;
        return p;
    }
    void display(link *p) {
        link* temp = p;//将temp指针重新指向头结点
        //只要temp指针指向的结点的next不是Null,就执行输出语句。
        while (temp->next) {
            temp = temp->next;
            printf("%d ", temp->elem);
        }
        printf("
    ");
    }
    

    结果:

    初始化链表为:
    1 2 3 4
    在第4的位置插入元素5:
    1 2 3 5 4
    删除元素3:
    1 2 5 4
    查找元素2的位置为:
    元素2的位置为:2
    更改第3的位置上的数据为7:
    1 2 7 4
    
  • 相关阅读:
    Python lambda函数
    python 获取日期
    <base>元素
    django--开发博客
    django修改时区,数据库
    django初探
    python创建虚拟环境
    资源记录页面
    组管理
    远程管理命令
  • 原文地址:https://www.cnblogs.com/wind-zhou/p/12895190.html
Copyright © 2011-2022 走看看