算法笔记--数据结构--链表
链表的概念
线性表
是一类很常用的数据结构,分为
- 线性表
- 链表
线性表相当于数组,在内存上是连续的且定长的
链表的存储位置一般是不连续的且长度可以改变
链表的结点通常有两部分组成,即数据域和指针域:
struct node P{
typename data; // 数据域,存放结点要存储的数据
node* next; // 指针域,指向下一个结点的地址
}
链表可根据是否有头结点,分为:
- 带头结点的链表,即头结点head的数据域不存放任何内容
- 不带头结点的链表
注意:最后一个结点的指针域应该指向NULL
内存的分配与释放
C语言中的malloc与free
头文件
#include<stdlib.h>
作用
- malloc用来申请动态内存,返回类型是申请的同变量类型的指针
- free用来释放内存空间
使用
typename* p = (typename*)malloc(sizeof(typename));
e.g
int* p = (int*)malloc(sizeof(int));
node* p = (node*)malloc(sizeof(node));
free(p);
注意
- malloc和free必须成对出现,否则容易产生内存泄露
- 当申请较大的内存空间时,可能会发生错误,返回空指针
int* p = (int*)malloc(1000000 * sizeof(int));
C++中的new与delete
头文件
不需要include
作用
- new用来申请动态内存,返回类型是申请的同变量类型的指针
- delete用来释放内存空间
使用
typename* p = new typename;
e.g.
int* p = new int;
node* p = new node;
delete(p);
注意
- new和delete必须成对出现,否则容易产生内存泄露
- 当申请较大的内存空间时,可能出错,如果申请失败,则会启动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
/*插入元素*/
/*参数:链表,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;
}
}
}
打印链表
/*显示链表*/
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
题目
解题思路
- 由于地址范围小,可以使用静态链表,依照题目要求,在结点的结构体中定义int型变量flag,表示结点是否在第一条链表中出现,是则为1,不是则为-1
- 遍历第一条链表,置所有结点flag为1
- 遍历第二题链表,当出现第一个flag为1的结点时,说明是在第一条链表中出现过的结果,即为两条链表的第一个共有结点
- 如果第二条链表枚举完仍然没有发现共有结点,则输出-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
题目
解题
#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