链表知识与部分题目

零.全文概述:

        从平时上机和作业的题目中可以看出,涉及到链表的题目基本上都是附加代码模式。

        所以要解决链表的问题,我们只用做到两点:

1.知道每道题目都要用到的基本操作函数怎么写

2.结合每道题的实际情况,去具体书写题目中要求的特殊函数

      例题1:

                                                                  

        

例题2: 

从例题1和例题2可以看出,提示中的单链表操作函数都是相同的,会在叁部分介绍。

而每道题还有他具体的逻辑和实现函数,会在肆部分介绍。

 

 

 

壹.前置知识

        a.         链表综述

​        链表是一种物理存储单元上非连续、非顺序的存储结构。

        数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

        链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 

        b.        struct结构体的介绍

        由前文我们可以知道,链表是许多节点被链条串在一起的一种数据结构。

        那自然的,我们就要表示每个节点。由于此时每个节点都要即包含数据,又要包含一个(指向下个节点的)指针 。——所以此时我们采用struct结构体去表示每个节点。

表示如下:

struct Node        
{
    int   data;    //这是数据域
    Node* next;    //这是指针域(表示一个指针指向下一个节点)
};

        结构体struct的定义即是struct 变量名字,再用一个括号(括号内包含了所需的指针域和数据域),特别注意的是括号最后一定要加上表示结尾的分号!!!

        对指针域的解释:此时指针是一个指向下一个节点的指针,所以指针的类型即是结构体的名字。故指针域的形式就是 结构体的名字+*(表示这是一个指针)+next(这是指针的名字,next指向下一个节点)

        另外要记得写struct的定义时是写在main函数外面的,即单独写

        c.        typedef的介绍

        typedef是c语言的一个关键字,可以使用它来为已有的数据类型取一个新的名字(即别名)。当你为某种数据类型取别名后,这个"别名"与该数据类型本来的名字是等价的。

        1.typedef的使用形式如下

typedef  数据类型  新的名字;

        #:要注意的是typedef的定义也是应该写在main函数外面的

        2.typedef的优点:        

        typedef可以把某个比较复杂的东西起一个简单的别名,可以在程序的书写中更加方便,同时好的别名也可以提高程序的可读性

        

        3.typedef在链表中的应用:

typedef Node* List;

        在链表中,我们习惯将struct中的指针域给typedef成List。(可以偷懒少打个指针符号)

        所以在随后的书写中,看到List我们要明白他就是Node*的别名,表示的是指向链表的指针数据类型

        d.        引用

        引用也是取别名。可以类比typedef去理解;只是typedef是给数据类型起别名,而引用是给变量取别名

        1.引用的定义与形式:

        引用不是新定义一个变量,而是给已存在变量取了一个别名,(编译器不会为引用变量开辟内存空间,它与引用的变量共用同一块内存空间)

类型 & 引用变量名 = 要引用的那个东西
eg:int & p = a;

        因此,变量a和他的引用p就是完完全全等价的东西。你对p进行的所有操作都会完全等价地作用于a,对a进行的操作同理也会完全等价地作用于p。他们就是一个东西

        2.引用在链表中应用

        在链表的学习中,我们会用到许多函数去实现其中的功能,但是我们知道函数的传参是一种值传递,我们在函数中进行的修改出了函数就会消失。

        因此我们利用引用就可以在函数中对链表进行操作了。

 例子:

int AddNode(List &list, int data)     
//由于此函数在list前加了&(引用),所以函数中对list的修改都会作用到传入的listA上
{
	List new_node = new Node;
	
	new_node->data = data;
	new_node->next = NULL;
	
	List temp = list;
	
	while(temp->next)	temp=temp->next;
	
	temp->next = new_node;
	
    return 0;
}

..........
..........


AddNode(listA,v);

        e.         new和delete

        我们知道,在链表的操作中,是要不断开辟新的节点和删除节点中,这个操作可以借用c++中的new和delete

        1.new

        new操作符在C++中用于动态分配内存,并返回一个指向新分配内存的指针

List new_node = new Node;

          其中:
                new后面跟着申请空间的类型,即告诉计算机我需要这么大的一块空间。此时我们是创建一个新的节点,所以new后面跟着的就是节点Node

                因为new是返回一个指针,这个指针类型就是我们申请空间的类型。此时即是Node*。所以我们要用一个节点指针List去接收他。

        2.delete

int DestroyList(List &list)
{
	
	while(list->next)
	{
		List sb =  list;
		delete sb;        //delete
		list=list->next;
	}
	
    return 0;
}

        delete是用于释放空间的(与new对应)

        操作为:delete+指针(指向要释放的那块空间)

        f.        指向结构的指针

        用->表示指针所指结构变量中的成员(打法是一个减号-加上一个大于号>)

  例:      

struct Node
{
    int data;		//数据域 
    Node* next;		//指针域 
};


.......
.......


int AddNode(List &list, int data)
{
	List new_node = new Node;
	
	new_node->data = data;
	new_node->next = NULL;
	
	List temp = list;
	
	while(temp->next)	temp=temp->next;
	
	temp->next = new_node;
	
    return 0;
}

               

         形式即为:

	new_node->data = data;    //即new_node结构体中的数据域
	new_node->next = NULL;    //即new_node结构体中的指针域

          其中new_node是一个节点指针(指向struct数据类型)

        我们知道new_node其中是包含数据域data和指针域next的;这是一个更方便地取其中数据域和指针域的方法

        形式为:

        变量名字->data:得到这个变量所包含的data数据域

        变量名字->next:得到这个变量所包含的next指针域

补:

       

        我们知道这里面new_node是一个指针,而常规的取指针中的值为: *指针名字

        而结构体常规取其中某个数据的方法为: 结构体名字.data / 结构体名字.next

        综上:

new_node->data   (*new_node).data;
new_node->next   (*new_node).next;

        这每一行的东西表示的是同一个意思

贰.重点

        a.        头节点的重要性

        要清楚,头节点是这个链表的唯一特征和标志

        在定义链表变量时,我们是设出一个链表的头节点,在初始化的过程中,我们也都是对链表头节点进行操作的

        无论是一个链表的创立初始化,还是将链表传入函数。 我们都是在对头节点进行操作

        而且在链表的插入 查找 删除中,我们也是从头节点入手;抓住头节点,顺着头节点去往下遍历寻找

        注意:

        头节点是很特殊的一个节点,他是只有指针域而没有数据域的,即头节点并不包含数据,他只有指向下一个节点的指针

 

        b.        链表的遍历

        在对链表的各个操作中,对链表进行遍历是较为常见的,形式如下

while(list->next)
{
	.........        //其中省略号是具体要进行的操作
	list=list->next; //往下进行遍历    
}

        解释:

        1.利用一个while循环去进行遍历,遍历结束的条件可以视情况而定,示例中就是list->next为NULL时停止循环(即当循环到链表尾端时)

        2.list=list->next; 是往下不断地进行遍历(list不断指向下一个节点)

叁.链表的一些基本操作函数:

        从上机和作业的题目中,可以看出题目基本上都有着相同的基本操作函数(以下四种)

        a.        初始化函数init

void InitList(List &list)
{
	list = new Node;        
	list->next = NULL;
	return ;
}


int main()
{

.......
.......

    List listA, listB;    //设出头节点listA和listB
	      
    InitList(listA); // 初始化单链表A(即对A的头 
    InitList(listB); // 初始化单链表
	 
........
........
}

 1.        定义变量链表listA和listB:

        从对头节点的理解可以知道,头节点就是我们识别链表的唯一特征标识。因此这里面我们定义新变量listA和链表listB;其实就是在设出他们的头节点listA和listB

      (按照北化的代码模式,这一步在主函数main中进行)

        做完这一步之后,只有两个空的指针变量,随后通过initlist函数对其进行初始化

2.        利用initlist函数进行初始化

        2.1函数传参的分析
void InitList(List &list)//通过引用,在initlist函数内去对头节点list初始化
        2.2分配空间
list = new Node;//利用关键字new,去给头节点指针list分配空间
        2.3使链表结构完备

        此时已经有了一个头节点head,但是还要有尾部指向NULL。由于此时链表相当于是空的,所以直接让头节点list指向NULL即可

list->next=NULL;	//让头节点指向空

        完成了初始化后,此时我们的链表如下图所示 

             

                

b.        插入函数AddNode(特指从尾部插入)

int AddNode(List &list, int data)
{
	List new_node = new Node;
	
	new_node->data = data;
	new_node->next = NULL;
	
	List temp = list;
	
	while(temp->next)	temp=temp->next;
	
	temp->next = new_node;
	
    return 0;
}

        1.函数传参的分析

int AddNode(List &list, int data)	//参量1:通过引用传入链表的头节点list,对其操作
									//参量2:要在尾部插入的数据data 

        2.创建一个新的节点,分配空间并赋值

List new_node = new Node;	//通过new去创建一个新的节点,分配空间 
	
new_node->data = data;	    //给这个新的节点赋值数据域和指针域 
new_node->next = NULL;

         赋值的分析:此时这个新节点应该是接在最后面的,因此他的指针域next就应该是指向NULL;而数据域显然就是我们传入的参数data

        3.找到新节点插入的位置

        由于是尾部插入,因此问题转化成了找到链表的最后一个位置。可以利用遍历进行

List temp = list;
	
while(temp->next)	temp=temp->next;
	
temp->next = new_node;

        利用一个临时指针temp去指向链表的头指针,随后用while进行遍历知道temp此时即为链表的最后一个元素;再让temp的下一个next指向我们新创的节点即可

        c.        打印链表函数PrintList

void PrintList(const List &list)   //加了个const的作用是保证了在函数内部不改变传入的这个链表
                                   //不过其实加不加都无所谓的
{
    list temp = list;
    
    while(temp->next)
    {
    	temp = temp->next;
    	cout<<temp->data<<" ";
    }
    
    return 0;

}

用一个临时指针temp去指向头节点,然后利用while在遍历的同时输出每个节点的值

注意 一开始的头节点是没有数据域的,所以不用输出头节点的数值

        d.        删除链表并释放空间DestroyList

int DestroyList(List &list)
{
	
	while(list->next)
	{
		List sb =  list;
		delete sb;
		list=list->next;
	}
	 
    return 0;
}

        从头节点开始,每经过一个节点就去用delete删除并释放它的内存;同时不断循环直到到达了尾部

肆.具体题目的介绍

        a.        题目一 :两个有序链表序列的合并(附加代码模式)

链接:两个有序链表序列的合并(附加代码模式)

题目:

代码实现:

#include <iostream>
#include <cstdio>

using namespace std;

struct Node
{
    int  data;
    Node* next;
};

typedef Node* List;

void InitList(List &list)
{
	list = new Node;
	list->next = NULL;
	return ;
}

void DestroyList(List &list)
{
	List temp = list ;
	while(temp->next)
	{
		List sb = temp;
		delete sb;
		temp = temp->next;
	}
	return ;
}

void AddNode(List &list,int data)
{
	List temp = list ;
	List add_node = new Node;
	
	add_node->data = data ;
	add_node->next = NULL ; 
	
	while(temp->next)
	{
		temp=temp->next;
	}
	
	temp->next = add_node;
	
	return ;
	
}

List MergeList(List &listA,List &listB)	//因为此时要返回一个头节点指针(代表答案链表),所以返回类型是List 
{
	//首先去创建一个新的答案链表(头节点),并将其初始化 
    List listC;    
    InitList(listC);
    
    //用两个链表指针分别指向两个链表的头部和尾部 
    List i = listA;
    List j = listB;
    
    while( i->next && j->next )		//跳出循环的前提是两个链表中有一个已经循环完了 
    {
    	//去比较大小 
    	if(i->next->data<=j->next->data)
		{
			AddNode(listC,i->next->data); //要注意头节点只是一个有地址和next的东西 是没有数据的 所以这里读入数据的时候 应该是i->next->data(要有一个next) 
			i=i->next;
		}	
    	else
		{
			AddNode(listC,j->next->data);
			j=j->next;
		}				
    }
    	//此时跳出了循环,说明A和B链表中有一个已经循环完了。所以此时如果另外一个链表还有东西的话,直接全部加到答案链表C的后面即可 
    while(i->next)
	{
        AddNode(listC,i->next->data);
        i=i->next;
    }
    while(j->next){
        AddNode(listC,j->next->data);
        j=j->next;
    }
    
    return listC;
}

void PrintList(const List &list)
{
    List temp=list;
    
    while(temp->next)
	{
        temp=temp->next;
        cout << temp->data << " ";
    }
    cout << endl;
    
}

int main(){
    int m,n;
    cin >> m >> n;
    
    List listA, listB;
	      
    InitList(listA); // 初始化单链表 
    InitList(listB); // 初始化单链表
	 
    for(int i=0;i<m;i++){
        int v;cin >> v;
        AddNode(listA,v); // 在单链表的末尾增加节点 
    }
    for(int i=0;i<n;i++){
        int v; cin >> v;
        AddNode(listB, v);
    }
    List listC = MergeList(listA,listB);    //合并两个单链表 
    
    PrintList(listC);   // 输出单链表 
    
    DestroyList(listA);  // 销毁单链表 
    DestroyList(listB);  // 销毁单链表 
    DestroyList(listC);  // 销毁单链表 
    
    return 0;
} 

只需要将叁中学习到的基本操作函数补充完整,同时书写一个函数对链表A,B进行比较后将其插入到链表C即可

         b.        题目二:两个有序链表序列的交集(附加代码模式)

链接:​​​​​​​

题目:

代码与思路:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
const int N = 1e5+10;

int a[N];

 
struct Node
{
    int data;		//数据域 
    Node* next;		//指针域 
};
 
typedef Node* List;	 
 
int InitList(List &list)
{
	list = new Node;	//利用new给头节点分配空间 
	list->next=NULL;	//让头节点指向空 
    return 0;
}
 
int DestroyList(List &list)
{
	
	while(list->next)
	{
		List sb =  list;
		delete sb;
		list=list->next;
	}
	 
    return 0;
}
 
void AddNode(List &list,int data)
{
	List temp = list ;
	List add_node = new Node;
	
	add_node->data = data ;
	add_node->next = NULL ; 
		
	if(data==-1)	return ;	//这就是这道题的特殊之处 当输入为-1时要return停止输入 
	
	while(temp->next)
	{
		temp=temp->next;
	}
	
	temp->next = add_node;
	
	return ;
	
}
 
 
List GetIntersection(const List &listA, const List &listB)	//因为此时要返回一个头节点指针(代表答案链表),所以返回类型是List
{
	//先将数组a初始化 
	memset(a,0,sizeof(a));
	
	//去创建一个新的答案链表(头节点),并将其初始化
	List listC;
	InitList(listC);
	
	//去对链表A进行一遍遍历,同时把其中每一个值都标记到数组a中 
	List i = listA;
	int t;
	while(i->next)
	{
		t=i->next->data;
		a[t]=1;
	}
	
	//去对链表B进行一遍遍历,把其中每个值去看看在数组a中是否标记为存在,若存在则存入链表C中 
	List j = listB;
    
     while(j->next)
	{
		t=j->next->data;
		if(a[t]==1)
		{
			AddNode(listC,t);
		}
	}
	
    return listC;
}

void PrintList(const List &list)
{
    list temp = list;
    
    while(temp->next)
    {
    	temp = temp->next;
    	cout<<temp->data<<" ";
    }
    
    return 0;
}

int main(){
    List listA, listB;      
    InitList(listA); // 初始化单链表 
    InitList(listB); // 初始化单链表 
    int v;
    
    while(cin >> v && v != -1){
        AddNode(listA,v); // 在单链表的末尾增加节点 
    }
    while(cin >> v && v != -1){
        AddNode(listB,v); // 在单链表的末尾增加节点 
    }
    
    List listC = GetIntersection(listA,listB);  //合并两个单链表 
    
    PrintList(listC);   // 输出单链表 
     
    DestroyList(listA);  // 销毁单链表 
    DestroyList(listB);  // 销毁单链表 
    DestroyList(listC);  // 销毁单链表 
     
    return 0;
} 

解释:

本题有两个特殊之处:

1.本题的输入有告知是在输入为-1的时候停止输入了,所以在AddNode函数中要加入一个if判断去判断是否有-1的输入

2.求交集的函数中:

运用了一个数组a去标记元素是否有出现过,如果元素x出现过,那么a【x】就会被标记为1。而若x未出现过,则a【x】标记为0

        c.        题目三:单链表反转(附加代码模式)

链接:​​​​​​​

题目:

代码实现:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

struct Node
{
    int data;		//数据域 
    Node* next;		//指针域 
};
 
typedef Node* List;	 
 
int InitList(List &list)
{
	list = new Node;	//利用new给头节点分配空间 
	list->next=NULL;	//让头节点指向空 
    return 0;
}
 
int DestroyList(List &list)
{
	
	while(list->next)
	{
		List sb =  list;
		delete sb;
		list=list->next;
	}
	 
    return 0;
}
 
void AddNode(List &list,int data)
{
	List temp = list ;
	List add_node = new Node;
	
	add_node->data = data ;
	add_node->next = NULL ; 
	
	while(temp->next)
	{
		temp=temp->next;
	}
	
	temp->next = add_node;
	
	return ;
	
}
 
void ReverseList(List &list) 
{
	List p, q;
	p 	 = list->next;
	list->next = NULL;
	while (p)
	{
		q = p;
		p = p->next;
		q->next = list->next;
		list->next = q;
 	}
 	
 	return ;
} 


void PrintList(const List &list){
    Node *p = list->next;
    if(p == NULL){
        cout<<"list is empty"<<endl;
    }else{
        while(p != NULL){
            cout << p->data << " ";
            p = p->next;
        }
    }
    cout << endl;
}

int main(){
    int n;
    while(cin >> n)
	{
        if( n ==0) 
		{
            cout<<"list is empty"<<endl;
            continue;
        }
        List list;      
        InitList(list); // 初始化单链表 
        for(int i=0;i<n;i++){
            int v;
            cin >> v;
            AddNode(list,v); // 在单链表的末尾增加节点 
        }
        
        PrintList(list);  // 输出链表
        ReverseList(list);  // 反转链表
        PrintList(list);    // 再次输出链表 
        DestroyList(list);  // 销毁单链表 
        
    }
    return 0;
} 

          

这里面唯一的不同就是反转链表的函数。

此中借助了两个辅助指针,以原链表中有三个数据节点为例子作图:

            

                        

本质是有利用两个辅助指针p和q去分别表示两个节点:

        进行while循环后,每次改变两个节点间的方向,让原先绿色的向右箭头去变成红色向左箭头方向;

        同时在这个过程中,不断更新list指针,让它始终指向新链表的首元素。然后通过迭代让q变成p,p=p->next变成新的p,从而把这节点一直不断地往后循环直至p为0。

        d.        题目四: 有序单链表删除重复元素

链接:有序单链表删除重复元素      

题目: 

代码实现:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
const int N = 1e5+10;

int a[N];

struct Node
{
    int data;		//数据域 
    Node* next;		//指针域 
};
 
typedef Node* List;	 
 
int InitList(List &list)
{
	list = new Node;	//利用new给头节点分配空间 
	list->next=NULL;	//让头节点指向空 
    return 0;
}
 
int DestroyList(List &list)
{
	
	while(list->next)
	{
		List sb =  list;
		delete sb;
		list=list->next;
	}
	 
    return 0;
}
 
void AddNode(List &list,int data)
{
	List temp = list ;
	List add_node = new Node;
	
	add_node->data = data ;
	add_node->next = NULL ; 
	
	while(temp->next)
	{
		temp=temp->next;
	}
	
	temp->next = add_node;
	
	return ;
	
}
 
void RemoveDuplicate(List &list)
{
	//由于有多次操作,所以先去把数组a初始化以下(即全部为0)
	memset(a,0,sizeof(a));
	
	List t = list; 
	while(t->next)
	{
		int x = t->next->data;
		if(a[x]==1)	//此时说明这个元素重复了,那么就要进行删除操作 
		{
			t->next = t->next->next; 	//删除操作 
		}
		else	a[x]=1;	//元素未出现则标记一下他出现过
	
		t = t->next;	//把指针往后移动一位 
		
	}
	
	return ;
}


void PrintList(const List &list){
    Node *p = list->next;
    if(p == NULL){
        cout<<"list is empty"<<endl;
    }else{
        while(p != NULL){
            cout << p->data << " ";
            p = p->next;
        }
    }
    cout << endl;
}

int main(){
    int n;
    while(cin >> n)
	{
        if( n ==0) {
            cout<<"list is empty"<<endl;
            continue;
        }
        List list;      
        InitList(list); // 初始化单链表 
        for(int i=0;i<n;i++){
            int v;
            cin >> v;
            AddNode(list,v); // 在单链表的末尾增加节点 
        }
        PrintList(list);  // 输出链表
        RemoveDuplicate(list);  // 删除重复元素 
        PrintList(list);    // 再次输出链表 
        DestroyList(list);  // 销毁单链表 
        
    }
    return 0;
} 

1. 用数组去进行标记:

        类比题目2,此时也是要判断数是否有出现在链表里过,所以我们也可以开一个数组(用memset初始化全为0)去标记。

                如果a【x】为0,说明没有出现过,则标记a【x】为1,表示已经出现过x这个数了         

                如果a【x】为1,说明已经出现过,则进行删除操作

2.链表的删除操作:

t->next = t->next->next; 	//删除操作 

不考虑释放内存的问题,则直接让next指针从指向下一个变成指向下下个即可

 图例删除掉了节点3

        f.        题目五:求链表的倒数第m个元素(附加代码模式)

链接:求链表的倒数第m个元素(附加代码模式)

题目:

 代码实现:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

struct Node
{
    int data;		//数据域 
    Node* next;		//指针域 
};
 
typedef Node* List;	 
 
int InitList(List &list)
{
	list = new Node;	//利用new给头节点分配空间 
	list->next=NULL;	//让头节点指向空 
    return 0;
}
 
int DestroyList(List &list)
{
	
	while(list->next)
	{
		List sb =  list;
		delete sb;
		list=list->next;
	}
	 
    return 0;
}
 
int AddNode(List &list,int data)
{
	List temp = list ;
	List add_node = new Node;
	
	add_node->data = data ;
	add_node->next = NULL ; 
	
	while(temp->next)
	{
		temp=temp->next;
	}
	
	temp->next = add_node;
	
	return 0;
	
}
 
// 查找倒数第m个节点
Node* FindMthNode(const List& list, int m)
{
	
	// 去求整个链表的长度 
    List t = list;
    int l = 0;
    while(t->next)
    {
    	t = t->next;
    	l++;
    }
    
    //	转为正数第几个 
    int x = l-m+1;	
    
    //去遍历找到那个指针
    t = list;
	while(x--)
	{
		t = t->next;
	} 
	
	return t;
}

int  main() 
{
    // freopen("/config/workspace/answer/in.txt","r",stdin);
    int n;
    cin >> n;
    List list;
    InitList(list);
    for(int i=0;i<n;i++){
        int v;
        cin >> v;
        int r = AddNode(list,v);
        if(r != 0) cout<<"add node failed"<<endl;
    }
    // PrintList(list);
    int m;
    cin >> m;
    Node* result = FindMthNode(list,m);
    if(result == NULL){
        cout << "failed to find the node" <<endl;
    }else{
        cout << result->data << endl;
    }
    DestroyList(list);
    return 0; 
}

比较直接的方法,求长度后再转为正数第几个。

  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值