数据结构学习笔记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("初始化链表为:\n");
    link *p = initLink();
    display(p);
    printf("在第4的位置插入元素5:\n");
    p = insertElem(p, 5, 4);
    display(p);
    printf("删除元素3:\n");
    p = delElem(p, 3);
    display(p);
    printf("查找元素2的位置为:\n");
    int address = selectElem(p, 2);
    if (address == -1) {
        printf("没有该元素");
    }
    else {
        printf("元素2的位置为:%d\n", address);
    }
    printf("更改第3的位置上的数据为7:\n");
    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("插入位置无效\n");
            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("没有该结点\n");
            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("\n");
}

结果:

初始化链表为:
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
posted @ 2020-05-15 15:24  wind-zhou  Views(352)  Comments(0Edit  收藏  举报