链式存储与顺序存储有一些不同的地方,而且比顺序存储要难。
在了解链式存储之前,我们先回顾一下指针。
一、指针
1、什么是指针
指针:是一个用来存放地址的变量,其值是指向另一个变量的地址。
所以说我认为完全可以把它当成普通的变量,只是普通的变量里存放的是数据如:int i=5,而指针里存放的就是一个地址,这个地址就能指向一个变量。
2、指针变量
1)、定义指针变量:int *p;
注意:这里的p是变量,就像int i中的i一样,而int *代表就是p是int 型的指针变量,就像i是int型的变量一样。
注意:指针变量一定要进行初始化,如int *p=NULL。否则p是野指针,会报错。
2)、程序中的*p(除定义外)
p指的的一个变量的地址,那么*p就是其存储的地址所指向的变量
示例如下:
#include <iostream>
using namespace std;
int main()
{
int a=5;
int *p=&a;
cout<<"&a="<<&a<<endl;
cout<<"p="<<p<<endl;
cout<<"a="<<a<<endl;
cout<<"*p="<<*p<<endl;
return 0;
}
输出:
3、指针与数组
1)、指针与数组名
我们先看下面这段代码:
#include <iostream>
using namespace std;
int main()
{
int *p;
int a[5]={0,1,2,3,4};
p=a;
for(int i=0;i<5;i++)
{
cout<<p[i]<<" ";
}
cout<<endl;
return 0;
}
输出结果为:
可见完全可以用p代表a,这是为什么呢?
因为a既是数组名也是指向整个元素的首地址,因此用p指向a,即可以用p访问数组a;
但是我想说:p指针是指针变量,而数组名a只是一个指针常量。
2)、指针数组
指针数组的本质是数组,数组中每一个成员是一个指针
如int *Array[5];
即代表:
二、链表
1、链表定义
链式存储和顺序存储的不同在于顺序存储用的是连续的地址,但是链式存储是用一组任意的存储单元存储数据的,地址可以是连续的也可以是不连续的。
为了能表示(存储)元素之间的逻辑关系(线性),在存放每个元素的同时,也存放相关元素的信息(相关元素的存储地址),即用指针来表示元素之间的逻辑关系。存放一个数据元素占用的空间为:
2、头结点
有时为了操作方便,在链表的第一个结点之前加一个“头结点”,该结点不存放元素,其指针指向线性表的第一个元素。
有了头结点后,对在第一个元素结点前插入结点和删除第一个结点,其操作与对其它结点的操作统一了。(不是必须的)
3、链表实现
我们用两个类来实现对链表的定义以及操作,一个是链表节点类LinkNode,用来定义一些指针节点以及初始化和分配内存;另一个是链表类,用来定义一些对于链表的操作。(链表类是链表节点类的友元)
1)、定义节点类
typedef int T;
class Linklist;
class LinkNode
{
friend class LinkList;//声名友元
private:
LinkNode *link;
T data;
public:
LinkNode(LinkNode *ptr=NULL)
{
link=ptr;
}
LinkNode(const T &item,LinkNode *ptr=NULL)//item是默认参数
{
data=item;
link=ptr;
}
~LinkNode(){};
};
注:默认参数必须是函数参数列表中最右边的参数,在函数调用的时候可以省略,当调用该函数时若实参的个数和定义时形参的个数不一致时,默认是从左往右匹配,所以默认参数要放在函数列表的最右边。在调用时,入股不写参数,则为第一个,如果写了一个T 型数据,则为第二个
2)、定义链表类
class LinkList
{
private:
LinkNode *first;
public:
LinkList();//构造函数
~LinkList();
int Length();//返回链表的长度
bool Insert(int i,T x);//向链表中插入元素
LinkNode *Locate(int i);//返回链表i处的节点值
void Input(T endTag);//按条件输入(顺序)
void Input2(T endTag);//按条件输入(逆序)
void PrintList();//打印链表
bool Delete(T &x);//删除值为x的节点
void Search(T &x);//按值查找
bool Find(T x);//查询链表中是否存在x
void Cross(LinkList &A,LinkList &B,LinkList &C);//链表的交操作
void Union(LinkList &A,LinkList &B);//链表的并操作
void Sort();//对链表进行排序
};
3)、构造函数
LinkList::LinkList()
{
first = new LinkNode;//定义一个头结点并进行初始化
first->data = 0;
first->link = NULL;
}
4)、析构函数
LinkList::~LinkList()
{
delete first;//删除头结点
}
5)、返回链表的长度
由于first是我们定义的头结点,所以我们应该将p设置为first的下一个结点,从这里开始计算长度。
int LinkList::Length()
{
int i=0;
LinkNode *p=first->link;
while(p!=NULL)
{
i++;
p=p->link;
}
return i;
}
6)、返回链表i处的节点值
LinkNode *LinkList::Locate(int i)
{
LinkNode *p=first;
int j=0;
if(i<0)
{
return NULL;
}
while(p!=NULL&&j<i)
{
p=p->link;
j++;
}
return p;
}
7)、向链表中插入元素
将p置于所要插入的结点的前一个位置,定义一个data值为x的新结点s,将s的下一个结点也指向p的下一个结点,再将p的下一个结点指向s
bool LinkList::Insert(int i,T x)
{
LinkNode *p=Locate(i-1);
if(p==NULL)
{
return false;
}
LinkNode *s=new LinkNode(x);
s->link=p->link;
p->link=s;
}
下面对部分操作进行分析:
s->link=p->link:将s指向的下一个结点变成p指向的下一个结点
p->link=s:将p的下一个结点指向s
8)、按条件输入(顺序)
新元素总是链接在后面,元素按自然顺序输入
void LinkList::Input(T endTag)
{
LinkNode *newNode,*r;
T val;
cin>>val;
r=first;
while(val!=endTag)
{
newNode=new LinkNode(val);//申请空间,存储读入的元素
r->link=newNode;
r=newNode;
cin>>val;
}
r->link=NULL;
}
9)、按条件输入(逆序)
新元素总是链接在前面,按相反的顺序输入
这个和插入很像
void LinkList::Input2(T endTag)
{
LinkNode *newNode,*r;
T val;
cin>>val;
r=first;
while(val!=endTag)
{
newNode=new LinkNode(val);
newNode->link=r->link;
r->link=newNode;
cin>>val;
}
}
10)、打印链表
void LinkList::PrintList()
{
LinkNode *p=first->link;
while(p!=NULL)
{
cout<<p->data<<" ";
p=p->link;
}
cout<<endl;
}
11)、删除值为x的节点
算法基本思想:找到第i-1个元素,使第i+1个元素成为其后继。释放第i个元素。
bool LinkList::Delete(T &x)
{
LinkNode * p, *q = NULL;
p = first->link;
if (p == NULL)
{
return false;
}
while (p->data!=x&&p->link!=NULL)
{
q = p;//找到第i-1个元素,并赋给q
p = p->link;//让p到第i个元素
}
if (p->data == x)
{
q->link = p->link;//让q的下一个元素直接指向p的下一个元素
delete p;
}
}
12)、按值查找
void LinkList::Search(T &x)
{
LinkNode *p=first->link;
int count=1,flag=0;
while(p!=NULL)
{
if(p->data==x)
{
flag=1;
break;
}
count++;
p=p->link;
}
if(flag)
{
cout<<x<<" is located at index of "<<count<<endl;
}
else
{
cout<<x<<" is not found"<<endl;
}
}
13)、查询链表中是否存在x
bool LinkList::Find(T x)
{
LinkNode *p=first;
while(p!=NULL)
{
if(p->data==x)
{
return true;
}
p=p->link;
}
return false;
}
14)、链表的交操作
在链表B中遍历,将值取出在链表A中寻找,如果找到,就在链表C中添加
void LinkList::Cross(LinkList &A,LinkList &B,LinkList &C)
{
T t;
for(int i=1; i<=B.Length(); i++)
{
t=B.Locate(i)->data;
if(A.Find(t))
{
C.Insert(C.Length()+1,t);
}
}
}
15)、链表的并操作
在链表B中遍历,将值取出在链表A中寻找,如果没找到,就在链表A尾部添加
void LinkList::Union(LinkList &A,LinkList &B)
{
T t;
for(int i=1; i<=B.Length(); i++)
{
t=B.Locate(i)->data;
if(!A.Find(t))
{
A.Insert(A.Length()+1,t);
}
}
}
16)、对链表进行排序
类似于冒泡排序
void LinkList::Sort()
{
LinkNode *p=first,*q=NULL;
while(p!=q)
{
while(p->link!=q)
{
if(p->data>p->link->data)
{
int t=p->data;
p->data=p->link->data;
p->link->data=t;
}
//如果没有找到满足if条件的,就一直往后遍历
//如果找到了,进行交换后再往后遍历
//一直到p->link!=q
p=p->link;
}
//将q指向p,在将p置为first头结点,从头再进行遍历操作
q=p;
p=first;
}
}
4、完整程序
#include <iostream>
#include <stdio.h>
using namespace std;
typedef int T;
class LinkList;
class LinkNode
{
friend class LinkList;
private:
LinkNode *link;
T data;
public:
LinkNode(LinkNode *ptr=NULL)
{
link=ptr;
}
LinkNode(const T &item,LinkNode *ptr=NULL)
{
data=item;
link=ptr;
}
~LinkNode() {};
};
class LinkList
{
private:
LinkNode *first;
public:
LinkList();//构造函数
~LinkList();
int Length();//返回链表的长度
bool Insert(int i,T x);//向链表中插入元素
LinkNode *Locate(int i);//返回链表i出的节点值
void Input(T endTag);//按条件输入(顺序)
void Input2(T endTag);//按条件输入(逆序)
void PrintList();//打印链表
bool Delete(T &x);//删除值为x的节点
void Search(T &x);//按值查找
bool Find(T x);//查询链表中是否存在x
void Cross(LinkList &A,LinkList &B,LinkList &C);//链表的交操作
void Union(LinkList &A,LinkList &B);//链表的并操作
void Sort();//对链表进行排序
};
LinkList::LinkList()
{
first = new LinkNode;
first->data = 0;
first->link = NULL;
}
LinkNode *LinkList::Locate(int i)
{
LinkNode *p=first;
int j=0;
if(i<0)
{
return NULL;
}
while(p!=NULL&&j<i)
{
p=p->link;
j++;
}
return p;
}
LinkList::~LinkList()
{
delete first;
}
bool LinkList::Insert(int i,T x)
{
LinkNode *p=Locate(i-1);
if(p==NULL)
{
return false;
}
LinkNode *newNode=new LinkNode(x);
newNode->link=p->link;
p->link=newNode;
}
void LinkList::Input(T endTag)
{
LinkNode *newNode,*r;
T val;
cin>>val;
r=first;
while(val!=endTag)
{
newNode=new LinkNode(val);
r->link=newNode;
r=newNode;
cin>>val;
}
r->link=NULL;
}
void LinkList::Input2(T endTag)
{
LinkNode *newNode,*r;
T val;
cin>>val;
r=first;
while(val!=endTag)
{
newNode=new LinkNode(val);
newNode->link=r->link;
r->link=newNode;
cin>>val;
}
}
void LinkList::PrintList()
{
LinkNode *p=first->link;
while(p!=NULL)
{
cout<<p->data<<" ";
p=p->link;
}
cout<<endl;
}
bool LinkList::Delete(T &x)
{
LinkNode * p, *q = NULL;
p = first->link;
if (p == NULL)
{
return false;
}
while (p->data!=x&&p->link!=NULL)
{
q = p;
p = p->link;
}
if (p->data == x)
{
q->link = p->link;
delete p;
}
}
int LinkList::Length()
{
int i=0;
LinkNode *p=first->link;
while(p!=NULL)
{
i++;
p=p->link;
}
return i;
}
void LinkList::Search(T &x)
{
LinkNode *p=first->link;
int count=1,flag=0;
while(p!=NULL)
{
if(p->data==x)
{
flag=1;
break;
}
count++;
p=p->link;
}
if(flag)
{
cout<<x<<" is located at index of "<<count<<endl;
}
else
{
cout<<x<<" is not found"<<endl;
}
}
bool LinkList::Find(T x)
{
LinkNode *p=first;
while(p!=NULL)
{
if(p->data==x)
{
return true;
}
p=p->link;
}
return false;
}
void LinkList::Cross(LinkList &A,LinkList &B,LinkList &C)
{
T t;
for(int i=1; i<=B.Length(); i++)
{
t=B.Locate(i)->data;
if(A.Find(t))
{
C.Insert(C.Length()+1,t);
}
}
}
void LinkList::Union(LinkList &A,LinkList &B)
{
T t;
for(int i=1; i<=B.Length(); i++)
{
t=B.Locate(i)->data;
if(!A.Find(t))
{
A.Insert(A.Length()+1,t);
}
}
}
void LinkList::Sort()
{
LinkNode *p=first,*q=NULL;
while(p!=q)
{
while(p->link!=q)
{
if(p->data>p->link->data)
{
int t=p->data;
p->data=p->link->data;
p->link->data=t;
}
p=p->link;
}
q=p;
p=first;
}
}
int main()
{
int n,m,t=0,a[10000],x1,y1,x2,x3,x4;
cin>>n;
LinkList p,q,r;
p.Input(n);
cout<<"A is created as: ";
p.PrintList();
cin>>x1>>y1;
p.Insert(x1,y1);
cout<<"After inserted A is ";
p.PrintList();
cin>>x2;
p.Delete(x2);
cout<<"After deleted A is ";
p.PrintList();
cin>>x3;
p.Search(x3);
cin>>x4;
p.Search(x4);
cin>>m;
q.Input(m);
cout<<"B is created as: ";
q.PrintList();
p.Cross(p,q,r);
cout<<"A cross B is ";
r.PrintList();
p.Union(p,q);
cout<<"A union B is ";
p.PrintList();
p.Sort();
cout<<"A union B in sequence is ";
p.PrintList();
return 0;
}
5、说明
1)、每次用结点类定义结点指针后一定要进行初始化
2)、链表尽量定义一个头结点,方便操作,但是头结点不属于整个链表。
3)、关于定义的节点初始化分配内存
如果我们想对插入某一个数时,先用结点类的构造函数对其初始化
LinkNode *newNode=new LinkNode(val),这样可以为该数申请空间,分配内存。