zoukankan      html  css  js  c++  java
  • 算法笔记--数据结构--链表

    链表的概念

    线性表是一类很常用的数据结构,分为

    • 线性表
    • 链表

    线性表相当于数组,在内存上是连续的且定长

    链表的存储位置一般是不连续的且长度可以改变

    Snipaste_2020-04-03_09-54-30

    链表的结点通常有两部分组成,即数据域和指针域:

    struct node P{
        typename data;	// 数据域,存放结点要存储的数据
        node* next;		// 指针域,指向下一个结点的地址
    }
    

    链表可根据是否有头结点,分为:

    1. 带头结点的链表,即头结点head的数据域不存放任何内容
    2. 不带头结点的链表

    注意:最后一个结点的指针域应该指向NULL

    内存的分配与释放

    C语言中的malloc与free

    头文件

    #include<stdlib.h>
    

    作用

    1. malloc用来申请动态内存,返回类型是申请的同变量类型的指针
    2. free用来释放内存空间

    使用

    typename* p = (typename*)malloc(sizeof(typename));
    e.g
        int* p  = (int*)malloc(sizeof(int));
    	node* p = (node*)malloc(sizeof(node));
    
    free(p);
    

    注意

    1. malloc和free必须成对出现,否则容易产生内存泄露
    2. 当申请较大的内存空间时,可能会发生错误,返回空指针
    int* p = (int*)malloc(1000000 * sizeof(int));
    

    C++中的new与delete

    头文件

    不需要include

    作用

    1. new用来申请动态内存,返回类型是申请的同变量类型的指针
    2. delete用来释放内存空间

    使用

    typename* p = new typename;
    e.g.
        int* p = new int;
    	node* p = new node;
    
    delete(p);
    

    注意

    1. new和delete必须成对出现,否则容易产生内存泄露
    2. 当申请较大的内存空间时,可能出错,如果申请失败,则会启动C++异常机制处理而不是返回空指针NULL
    int* p = new int[1000000];
    

    链表的基本操作

    创建链表

    将零散的结点串接起来

    注意pre、p、head三枚指针直接的操作关系

    /*创建链表*/
    node* creat(int MyArray[], int length){ // 传入数组,length为数组长度
        node *pre, *p;          // pre保存当前结点的前驱结点,p为当前节点
        node* head = new node;  // head为头结点
        head->next = NULL;
        pre = head;
        for(int i = 0; i < length; i++){
            p = new node;           // 创建结点,并把地址赋给p
            p->data = MyArray[i];
            p->next = NULL;
            pre->next = p;
            pre = p;
        }
        return head;
    }
    

    查找元素

    /*查找元素*/
    /*参数:头结点的链表,x为所要查找的元素*/
    /*返回值为所查找的元素的个数*/
    int searchElement(node *head, int x){
        node* p = head->next;
        int counter = 0;        // 统计x出现的次数
        while(p != NULL){   // 只要没有到达链尾
            if(p->data == x)
                counter++;
            p = p->next;    // 指针移向下一个元素
        }
        return counter;
    }
    

    插入元素

    在第n个位置插入x:即在插入完成后第n个位置的元素是x

    Snipaste_2020-04-03_11-51-06

    /*插入元素*/
    /*参数:链表,pos为插入位置,x为插入值*/
    /*head的链表会自动变化*/
    void insertElement(node* head, int pos, int x){
        node* p;
        p = head;
        for(int i = 0; i < pos - 1; i++){   // 用指针p找到pos的前一个位置
            p = p->next;
        }
        node* q = new node;     // 新建结点
        q->data = x;            // 新结点的数据域为x
        q->next = p->next;      // 新结点的下一个结点指向原先插入位置的结点
        p->next = q;            // 前一个位置的结点指向新结点
    }
    

    删除元素

    Snipaste_2020-04-03_11-52-02

    /*删除结点*/
    void deleteElement(node* head, int x){
        node *p, *pre;      // p指向当前结点,pre指向当前结点的前一个结点
        p = head->next;
        pre = head;
        while(p != NULL){
            if(p->data == x){           // 找到则删除
                pre->next = p->next;
                delete(p);
                p = pre->next;
            }else{                      // 没找到,pre和p都向后移动
                pre = p;
                p = p->next;
            }
        }
    }
    

    打印链表

    /*显示链表*/
    void printList(node* head){
        node* p = head->next;
        while(p != NULL){
            printf("%d ", p->data);
            p = p->next;
        }
    }
    

    代码汇总

    #include<iostream>
    using namespace std;
    
    struct node{
        int data;       // 数据域
        node* next;     // 指针域
    };
    
    /*显示链表*/
    void printList(node* head){
        node* p = head->next;
        while(p != NULL){
            printf("%d ", p->data);
            p = p->next;
        }
    }
    
    /*创建链表*/
    node* creat(int MyArray[], int length){ // 传入数组,length为数组长度
        node *pre, *p;          // pre保存当前结点的前驱结点,p为当前节点
        node* head = new node;  // head为头结点
        head->next = NULL;
        pre = head;
        for(int i = 0; i < length; i++){
            p = new node;           // 创建结点,并把地址赋给p
            p->data = MyArray[i];
            p->next = NULL;
            pre->next = p;
            pre = p;
        }
        return head;
    }
    
    /*查找元素*/
    /*参数:头结点的链表,x为所要查找的元素*/
    /*返回值为所查找的元素的个数*/
    int searchElement(node *head, int x){
        node* p = head->next;
        int counter = 0;        // 统计x出现的次数
        while(p != NULL){   // 只要没有到达链尾
            if(p->data == x)
                counter++;
            p = p->next;    // 指针移向下一个元素
        }
        return counter;
    }
    
    /*插入元素*/
    /*参数:链表,pos为插入位置,x为插入值*/
    /*head的链表会自动变化*/
    void insertElement(node* head, int pos, int x){
        node* p;
        p = head;
        for(int i = 0; i < pos - 1; i++){   // 用指针p找到pos的前一个位置
            p = p->next;
        }
        node* q = new node;     // 新建结点
        q->data = x;            // 新结点的数据域为x
        q->next = p->next;      // 新结点的下一个结点指向原先插入位置的结点
        p->next = q;            // 前一个位置的结点指向新结点
    }
    
    /*删除结点*/
    void deleteElement(node* head, int x){
        node *p, *pre;      // p指向当前结点,pre指向当前结点的前一个结点
        p = head->next;
        pre = head;
        while(p != NULL){
            if(p->data == x){           // 找到则删除
                pre->next = p->next;
                delete(p);
                p = pre->next;
            }else{                      // 没找到,pre和p都向后移动
                pre = p;
                p = p->next;
            }
        }
    
    }
    
    
    int main(){
        int MyArray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        node* head;
        head = creat(MyArray, 10);
        printList(head);
        insertElement(head, 3, 3);
        printf("
    在第%d位置插入%d后
    ", 3, 3);
        printList(head);
        printf("
    查询%d出现的次数: %d", 3, searchElement(head, 3));
        deleteElement(head, 7);
        printf("
    删除%d后
    ", 7);
        printList(head);
        return 0;
    }
    

    静态链表

    ​ 对于有些问题来说,结点的地址是比较小的整数,这样就没有必要去创建动态链表,而应该使用静态链表

    ​ 静态链表的实现原理是hash,通过建立一个结构体数组,并令数组的下标直接表示结点的地址,来达到直接访问数组中的元素就能访问结点的效果。由于结点的访问非常方便,因此静态链表是不需要结点的

    struct Node{
        typename data;	// 数据域
        int next;		// 指针域
    }node[SIZE];
    
    #include<iostream>
    using namespace std;
    
    struct Node{
        int data;
        int next;
    }node[100000];
    
    int main(){
        node[11111].next = 22222;
        node[22222].next = 33333;
        node[33333].next = -1;
        return 0;
    }
    

    实例 [PAT A1032] Sharing

    题目

    PAT A1032

    解题思路

    1. 由于地址范围小,可以使用静态链表,依照题目要求,在结点的结构体中定义int型变量flag,表示结点是否在第一条链表中出现,是则为1,不是则为-1
    2. 遍历第一条链表,置所有结点flag为1
    3. 遍历第二题链表,当出现第一个flag为1的结点时,说明是在第一条链表中出现过的结果,即为两条链表的第一个共有结点
    4. 如果第二条链表枚举完仍然没有发现共有结点,则输出-1

    代码

    #include<cstdio>
    using namespace std;
    
    const int SIZE = 100010;
    
    struct Node{
        char data;
        int next;
        bool flag;
    }node[SIZE];
    
    int main(){
        for(int i = 0; i < SIZE; i++)
            node[i].flag = false;
    
        int addr1, addr2, n;
        scanf("%d%d%d", &addr1, &addr2, &n);
        char data;
        int address, next;
        for(int i = 0; i < n; i++){
            scanf("%d %c %d", &address, &data, &next);
            node[address].data = data;
            node[address].next = next;
        }
    
        // 遍历链表1
        int p;
        for(p = addr1; p != -1; p = node[p].next){
            node[p].flag = true;
        }
        for(p = addr2; p != -1; p = node[p].next){
            if(node[p].flag == true) break;
        }
    
        if(p != -1)
            printf("%05d
    ", p);
        else
            printf("-1
    ");
    
    
        return 0;
    }
    

    静态链表解题模板

    • 1️⃣定义静态链表
    struct Node{
        int address;	// 结点地址
        typename data;	// 数据域
        int next; 		// 指针域
        XXX;			// 结点的某个性质,不同的题目设置不同
    }
    
    • 2️⃣对静态链表初始化,一般来说,需要对定义中的XXX进行初始化,将其定义为正常情况下达不到的数字。例如,对结点是否在链表上这个性质来说,我们可以初始化为0,表示结点不在链表上
    for(int i = 0; i < maxn; i++){
        node[i].XXX = 0;
    }
    
    • 3️⃣根据题目给的首地址,遍历整条链表,并对XXX进行标记,并且对有效结点的个数进行计数
    int p = begin, count = 0;
    while(p != -1){
        node[i].XXX = 1;
        count++;
        p = node[p]->next;
    }
    
    • 4️⃣静态链表是利用地址映射(hash)的方式实现的,所以,数组的很多位置是空的,可以利用排序将有效结点移动到左边,空结点在右边,利用XXX的值可以通过sort的cmp进行实现
    bool cmp(Node a, Node b){
        if(a.XXX == -1 || b.XXX == -1){
            // 至少一个结点是无效结点,就把它放在数组后面
            return a.XXX > b.XXX;
        }else{
            // 二级排序
        }
    }
    
    • 5️⃣已有了排序后的数组,根据题目具体要求进行实现

    [PAT A1052] Linked List Sorting

    题目

    PAT A1052

    解题

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int maxn = 100005;
    // 定义结构体
    struct Node {
        int address;
        int data;
        int next;
        bool flag;
    }node[maxn];
    // 用于sort的cmp函数
    bool cmp(Node a, Node b){
        if(a.flag == false || b.flag == false){
            return a.flag > b.flag;         // 只要a和b中有一个无效结点,就把它放后面
        }else {
            return a.data < b.data;         // 如果都是有效结点,则按要求排序
        }
    }
    
    int main(){
        // 初始化链表的flag
        for(int i = 0; i < maxn; i++){
            node[i].flag = false;
        }
        // 根据题目输入
        int n, begins, address;
        scanf("%d%d", &n, &begins);
        for(int i = 0; i < n; i++){
            scanf("%d%d%d", &node[address].address, &node[address].data, &node[address].next);
        }
        // 从头结点遍历链表并且修改flag
        int counter = 0, p = begins;
        while(p != -1){
            node[p].flag = true;
            counter++;          // 题目输入结点可能是不在链表上的
            p = node[p].next;
        }
    
        if(counter == 0){
            printf("0 -1");     // 特殊情况
        }else{
            sort(node, node + maxn, cmp);   // sort,会自动改变地址
            printf("%d %05d", counter, node[0].address);
            for(int i = 0; i < counter; i++){
                if(i != counter - 1){        // 用于控制是否为最后一个值
                    printf("%05d %d %05d
    ", node[i].address, node[i].data, node[i+1].address);
                }else{
                    printf("%05d %d -1
    ", node[i].address, node[i].data);
                }
            }
        }
        return 0;
    }
    

    Write by Gqq

  • 相关阅读:
    [LeetCode] Baseball Game
    [Linux] Shell Scripts
    [Linux] 正则表达式与文件格式化处理
    [Linux] 学习bash
    [Linux] vim程序编辑器
    [Linux] 文件与文件系统的压缩打包与备份
    [LeetCode] Reverse Words in a String
    [LeetCode] Reverse Integer
    [国嵌笔记][017][Makefile工程管理]
    [国嵌笔记][016][交叉工具链]
  • 原文地址:https://www.cnblogs.com/zgqcn/p/12627669.html
Copyright © 2011-2022 走看看