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
    
  • 相关阅读:
    Java实现 蓝桥杯 历届试题 连号区间数
    Java实现 蓝桥杯 历届试题 连号区间数
    Java实现 蓝桥杯 历届试题 连号区间数
    Java实现 蓝桥杯 历届试题 连号区间数
    Java实现 蓝桥杯 历届试题 连号区间数
    Java实现 蓝桥杯 历届试题 大臣的旅费
    Java实现 蓝桥杯 历届试题 大臣的旅费
    Java实现 蓝桥杯 历届试题 大臣的旅费
    Java实现 蓝桥杯 历届试题 大臣的旅费
    Navicat查询哪些表有指定字段名
  • 原文地址:https://www.cnblogs.com/wind-zhou/p/12895190.html
Copyright © 2011-2022 走看看