Loading

算法笔记--数据结构--链表

链表的概念

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

  • 线性表
  • 链表

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

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

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("\n在第%d位置插入%d后\n", 3, 3);
    printList(head);
    printf("\n查询%d出现的次数: %d", 3, searchElement(head, 3));
    deleteElement(head, 7);
    printf("\n删除%d后\n", 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\n", p);
    else
        printf("-1\n");


    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\n", node[i].address, node[i].data, node[i+1].address);
            }else{
                printf("%05d %d -1\n", node[i].address, node[i].data);
            }
        }
    }
    return 0;
}

Write by Gqq

posted @ 2020-04-03 17:19  ZHGQCN  阅读(178)  评论(0编辑  收藏  举报