【算法笔记】链表处理:PAT A1032 sharing | PAT A1052 Linked list sorting
链表
链表由若干个结点组成,且结点在内存中的存储位置通常不连续。
链表结点一般由两部分组成,数据域和指针域。
struct node{
typename data; //数据域
node* next; //指针域
};
数据域存放结点要存储的数据,指针域指向下一个结点的地址,头结点head数据域data不存放任何内容,而指针域next指向第一个数据域有内容的结点。链表最后一个结点的next指针指向NULL,即空地址,表示一条链表的结尾。
使用malloc函数或new运算符为链表结点分配内存空间
malloc函数
头文件:stdlib.h
,是c语言用于申请动态内存的函数,返回类型是申请的同变量类型的指针。
typename* p=(typename*)malloc(sizeof(typename));
int* p=(int*)malloc(sizeof(int));
node* p=(node*)malloc(sizeof(node));
sizeof(node):需要申请的内存空间大小
malloc函数会向内存申请空间,返回指向这块空间的指针(未确定类型的指针void*)
(node*):强制转换指针类型
申请得到的node类型大小的内存空间,通过指针p访问它。如果申请失败,则会返回空指针NILL。
一般来说,如果只是申请一个链表结点是不会失败的,失败一般发生在使用malloc申请了较大的动态数组:
int* p=(int*)malloc(1000000 * sizeof(int));
new运算符
用于c++,申请动态空间。
typename* p=new typename;
int* p=new int;
node* p=new node;
如果申请失败,则会启动c++异常机制处理而不是返回空指针NULL。和malloc同理,如果使用了new申请较大的动态数组,会发生异常,并在没有特殊处理的情况下直接退出程序。
内存泄漏
开辟出来的内存空间在使用过后没有释放,导致其在程序结束之前始终占据该内存空间,这在一些较大的程序中很容易导致内存消耗过快以至最后无内存可分配。
如何释放malloc与new开辟出来的空间:
- free函数
对应malloc函数,主要实现两个效果:释放指针变量p所指向的内存空间;将指针变量p指向空地址NULL。
free(p);
- delete运算符
对应new函数,delete(p);
链表的基本操作
创建链表
最直观的写法:
node* node1=new node;
node* node2=new node;
node* node3=new node;
node* node4=new node;
node* node5=new node;
node1->data=5;
node1->next=node2;
node1->data=3;
node1->next=node3;
node1->data=6;
node1->next=node4;
node1->data=1;
node1->next=node5;
node1->data=2;
node1->next=NULL;
一般使用for循环来建立需要的链表:
#include <cstdio>
#include <stdlib.h>
#include <iostream>
struct node{
int data;
node* next;
};
//创建链表
node* create(int Array[]){
node *p,*pre,*head;
head=new node; //创建头结点
head->next=NULL; //头结点不需要数据域;指针域初始值为空
pre=head; //前驱结点为头结点
for(int i=0;i<5;i++){
p=new node; //新建结点
//将Array[i]赋给新建的结点作为数据域,也可scanf输入
p->data=Array[i];
p->next=NULL;
pre->next=p;
pre=p; //把pre设置为p,作为下一个结点的前驱结点
}
return head; //返回头结点指针
}
int main(){
int Array[5]={
5,3,6,1,2
};
node* L=create(Array); //新建链表,返回的头指针给L
L=L->next; //从第一个结点开始有数据域
while(L!=NULL){
printf("%d",L->data);
L=L->next;
}
return 0;
}
查找元素
查找是否有给定的元素x:
从第一个结点开始,不断判断当前结点的数据域是否等于x,如果等于,就给计数器count加1。
int search(node* head,int x){
int count=0; //计数器
node* p=head->next; //指针指向第一个结点
while(p!=NULL){ //没有到达链表末尾
if(p->data==x) {count++;}
p=p->next;
}
return count;
}
插入元素
先把待插入元素的next指向插入位置的后一个地址,再把插入位置的前一个元素的next指向待插入元素的地址。
//插入元素,无返回值
//将x插入以head为头结点的链表的第pos个位置上
void insert(node* head,int pos,int x){
node* p=head; //声明一个指针指向头结点
for(int i=0;i<pos-1;i++){
p=p->next; //指针找到待插入位置的前一个位置
}
node* q=new node; //新建一个结点
q->data=x; //x放入
//连接
q->next=p->next;
p->next=q;
}
删除元素
删除链表上所有值为给定的数x。
删除位置的前驱结点指向删除位的下一个地址,释放删除位的内存空间
//删除以head为头结点的链表中所有数据域为x的结点
void del(node* head,int x){
node* p=head->next; //枚举指针
node* pre=head; //p的前驱指针
while(p!=NULL){
if(p->data==x){
pre->next=p->next;
delete(p);
p=pre->next;
}
else{
pre=p;
p=p->next;
}
}
}
静态链表
静态链表的实现原理是hash,通过建立一个结构体数组,并令数组的下标直接表示结点的地址,来达到直接访问数组中的元素就能访问结点的效果。
静态链表不需要头结点。
struct Node{
typename data; //数据域
int next; //指针域,存放下一个结点的地址,事实上就是数组下标
}node[size];
如:
node[11111]=22222;
node[22222]=33333;
node[33333]=-1; //没有后继结点
一般情况下,结构体类型名和结构体变量名可以相同,但由于静态链表是由数组实现,就有可能需要对其进行排序,如果结构体类型名和结构体变量名相同,sort函数会报错。在使用静态链表时,尽量不要把结构体类型名和结构体变量名取成相同的名字。
【PAT A1032】 Sharing
题意:
给出两条链表的首地址以及若干结点的地址、数据、下一个结点的地址,求两条链表的首个共用结点的地址。如果两条链表没有共用结点,则输出-1。
思路
- 由于地址范围很小,因此可以直接用静态链表。需要在结点的结构体中再定义一个int型变量flag,表示结点是否在第一条链表中出现,是则为1,不是为-1。
- 从第一条链表的首地址出发遍历第一条链表,将经过的所有结点flag值赋1。
- 接下来枚举第二条链表,当出现第一个flag值为1的结点,说明是第一条链表中出现过的结果,即为两条链表的第一个共用结点。
- 如果第二条链表枚举完仍没有发现共用结点,则输出-1。
【注意:】
使用%05d格式输出地址
scanf使用%c格式时可以读入空格,因此在输入地址、数据、后继结点地址时,格式不能写成%d%c%d,必须在中间加空格。
代码
#include <cstdio>
#include <cstring>
const int maxn=100010;
struct NODE{
char data;
int next;
bool flag;
}node[maxn];
int main(){
for(int i=0;i<maxn;i++){
node[i].flag=false;
}
int s1,s2,n; //s1,s2 链表首地址,n 元素总数
scanf("%d%d%d",&s1,&s2,&n);
int address,next; //首地址,后继地址
char data; //数据
//录入结点首地址,数据,后继地址
for(int i=0;i<n;i++){
scanf("%d %c %d",&address,&data,&next);
node[address].data=data; //地址是数组下标
node[address].next=next;
}
int p; //保存结点地址
//枚举第一条链表的所有结点,flag置true
for(p=s1;p!=-1;p=node[p].next){
node[p].flag=true;
}
for(p=s2;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;
}
【PAT A1052】 Linked list sorting
题意:给出N个结点的地址address、数据域data以及指针域next,然后给出链表的首地址,要求把在这个链表上的结点按data值从小到大输出
思路
- 定义静态链表,结点性质由bool型变量flag定义,表示为结点在链表中是否出现,flag为false表示无效结点。
- 初始化,flag置为false
- 由链表首地址begin遍历整条链表,并标记有效结点的flag为true,同时统计有效结点的个数count。
- 对结点进行排序,cmp原则是:如果cmp的两个参数结点中有无效结点,则按flag从大到小排序,以把有效结点排到数组左端,否则按数据域从小到大排序。
- 输出有效结点。
代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1000010;
//定义静态链表
struct Node{
int address,data,next;
bool flag;
}node[maxn];
//排序
bool cmp(Node a,Node b){
//如果ab中有无效结点
if(a.flag == false || b.flag == false) return a.flag>b.flag;
//否则按结点数值大小排序
else return a.data<b.data;
}
int main(){
for(int i=0;i<maxn;i++){
node[i].flag=false;
}
int head,n,count=0; //链表首地址,元素个数
scanf("%d%d",&n,&head);
int address,data,next;
for(int i=0;i<n;i++){
scanf("%d%d%d",&address,&data,&next);
node[address].data=data;
node[address].next=next;
node[address].address=address;
}
int p;
for(p=head;p!=-1;p=node[p].next){
node[p].flag=true;
count++;
}
if(count==0){
printf("0 -1");
}else{
sort(node,node+maxn,cmp);
printf("%d %05d\n",count,node[0].address);
for(int i=0;i<count;i++){
if(i!=count-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;
}