线性表

 

线性表

  • 线性表是由n(n>=0)个相同类型的数据元素组成的有限序列,它是最基本、最常用的一种线性结构。顾名思义,线性表就像是一条线,不会分叉。线性表有唯一的开始和结束,除了第一个元素外,每个元素都有唯一的直接前驱:除了最后一个元素外,每个元素都有唯一的直接后继,如图所示。

 

  • 线性表有两种存储方式:顺序存储和链式存储。采用顺序存储的线性表称为顺序表,采用链式存储的线性表称为链表。链表又分为单链表、双向链表和循环链表。

顺序表

  • 顺序表采用顺序存储方式,即逻辑上相邻的数据在计算机内的存储位置也是相邻的
  • 顺序存储方式,元素存储是连续的,中间不允许有空,可以快速定位第几个元素,但是插入和删除时需要移动大量元素。
  • 根据分配空间方法不同,顺序表可以分为静态分配和动态分配两种方法。

静态分配

顺序表最简单的方法是使用一个定长数组data[]存储数据,最大空间为Maxsize,用length记录实际的元素个数,即顺序表的长度。这种用定长数组存储的方法称为静态分配。静态顺序表如图所示。

 

顺序表的静态分配结构体定义,如图所示。采用静态分配方法,定长数组需要预先分配一段固定大小的连续空间,但是在运算的过程中,如合并、插入等操作,容易超过预分配的空间长度,出现溢出。解决静态分配的溢出问题,可以采用动态分配的方法

 

动态分配

在程序运行过程中,根据需要动态分配一段连续的空间(大小为Maxsize),用elem记录该空间的基地址(首地址),用length记录实际的元素个数,即顺序表的长度。采用动态存储方法,在运算过程中,如果发生溢出,可以另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充存储空间的目的

 

问题1,使用typedef有什么好处?

typedef是C/C++语言的关键字,用于给原有数据类型起一个别名,在程序中可以等价使用,语法规则如下。

typedef 类型名称 类型标识符

 

  • “类型名称”为已知数据类型,包括基本数据类型(如int、float等)和用户自定义数据类型(如用struct自定义的结构体)。
  • “类型标识符”是为原有数据类型起的别名,需要满足标识符命名规则。就像给某个人起一个小名或绰号一样

问题2:为什么使用ElemType作为数据类型?

使用ElemType是为了让算法的通用性更好,因为使用线性表的结构体定义后,并不清楚具体问题处理的数据是什么类型,不能简单地写成某一种类型。结合typedef使用,可以提高算法的通用性和可移植性。

顺序表的基本操作

初始化是指为顺序表分配一段预定义大小的连续空间,用elem记录这段空间的基地址,当前空间内没有任何数据元素,因此元素的实际个数为0。假设我们已经预定义了一个最大空间数Maxsize,那么就用new分配大小为Maxsize的空间,分配成功会返回空间的首地址,分配失败会返回空指针。

 

#include <iostream>
using namespace std; 
#define Maxsize 100//最大空间 
typedef struct {
	int *elem;//基地址,前面加*表示取地址中的内容 
	int length;//顺序表的长度 
}SqlList;
bool InitList(SqlList &L){//构造一个空的顺序表L 
	//L前加&表示引用参数,函数内部的改变跳出函数后仍然有效
	//如果不加&,函数内部改变跳出函数后无效
	L.elem=new int[Maxsize];//为顺序表动态分配Maxsize个空间
	if(!L.elem)return false;//分配空间失败
	L.length=0;//顺序表长度为0 
	return true; 
}
bool CreateList(SqlList &L){//创建一个顺序表 
	int x,i=0;
	cin >>x;
	while(x!=-1){//当输入-1时结束,也可以设置其他的条件结束
	if(L.length==Maxsize){
		cout <<"顺序表已满";
		return false;
	} 
	L.elem[i++]=x; //将数据存入第i个位置,然后i++
	L.length++;//顺序表长度加1 
	cin >>x;//继续输入 
	}	
	return true;
} 
bool GetElem(SqlList L,int i,int &e){
	if(i<1||i>L.length)return false;
	e=L.elem[i-1];
	return true;
}
int LocateElem(SqlList L,int e){
	for(int i=0;i<L.length;i++){
		if(L.elem[i]==e)return i+1;//下标为i,实际为i+1个元素 
	}
	return -1;
}
int InsertElem(SqlList &L,int i,int e){
	if(i<1||i>L.length+1)return false;
	if(L.length==Maxsize)return false;
	for(int j=L.length-1;j>=i-1;j--){
		L.elem[j+1]=L.elem[j];
	}
	L.elem[i-1]=e;
	L.length++;
	return true;
} 
bool DeleteElem(SqlList &L,int i,int &e){
	if(i<1||i>L.length)return false;
	e = L.elem[i-1];//将欲删除的元素保存在e中 
	for(int j=i;j<=L.length-1;j++){
		L.elem[j-1]=L.elem[j];
	}
	L.length--; 
	return true; 
}
void print(SqlList L)
{
	cout<<"输出顺序表"<<endl;
	for(int j=0;j<=L.length-1;j++)
	     cout<<L.elem[j]<<"   ";
	cout<<endl;
}

void DestroyList(SqlList &L)
{
	//使用new申请的内存,释放时用delete,使用new [ ]申请的内存释放时要用delete [ ] 
	if (L.elem) delete []L.elem;    //释放存储空间
}
int main()
{
    SqlList myL;
    int i,e,x;
    cout << "1. 初始化\n";
	cout << "2. 创建\n";
	cout << "3. 取值\n";
	cout << "4. 查找\n";
	cout << "5. 插入\n";
	cout << "6. 删除\n";
	cout << "7. 输出\n";
	cout << "8. 销毁\n";
	cout << "0. 退出\n";
	int choose=-1;
	while(choose!= 0)
	{
        cout<<"请选择:";
		cin>>choose;
		switch(choose)
		{
		    case 1://初始化顺序表
		        cout<<"顺序表初始化..."<<endl;
		        if(InitList(myL))
                    cout<<"顺序表初始化成功!"<<endl;
                else
                    cout<<"顺序表初始化失败!"<<endl;
		        break;
		     case 2://创建顺序表
		         cout<<"顺序表创建..."<<endl;
		         cout<<"输入整型数,输入-1结束"<<endl;
		         if(CreateList(myL))
                    cout<<"顺序表创建成功!"<<endl;
                 else
                    cout<<"顺序表创建失败!"<<endl;
                 break;
            case 3://取值
                cout <<"输入整型数i,取第i个元素输出"<<endl;
                cin>>i;
                if(GetElem(myL,i,e))
                    cout<<"第i个元素是: "<<e<<endl;
                 else
                    cout<<"顺序表取值失败!"<<endl;;
                cout<<"第i个元素是: "<<e<<endl;
                break;
            case 4://查找
                cout << "请输入要查找的数x:";
                cin>>x;
                if(LocateElem(myL,x)==-1)
                    cout<<"查找失败!"<<endl;
                else
                    cout<<"查找成功!"<<endl;
                break;
            case 5://插入
                cout<<"请输入要插入的位置和要插入的数据元素e:";
                cin>>i>>e;
                if(InsertElem(myL,i,e))
                    cout<<"插入成功!"<< endl;
                else
                    cout<<"插入失败!"<<endl;
                break;
             case 6://删除
                cout<<"请输入要删除的位置i:";
                cin>>i;
                if(DeleteElem(myL,i,e))
                    cout<<" 删除成功!"<<endl;
                else
                    cout<<"删除失败!"<<endl;
                break;
            case 7://输出
                print(myL);
                break;
            case 8://销毁
                cout<<"顺序表销毁..."<<endl;
                DestroyList(myL);
                break;
        }
	}
    return 0;
}

单链表

 

链表是线性表的链式存储方式。逻辑上相邻的数据在计算机内的存储位置不一定相邻。那么怎么表示逻辑上的相邻关系呢

存储方式

可以给每个元素附加一个指针域,指向下一个元素的存储位置,如图

从图中可以看出,每个节点包含两个域:数据域和指针域。数据域存储数据元素,指针域存储下一个节点的地址,因此指针指向的类型也是节点类型。每个指针都指向下一个节点,都是朝一个方向的,这样的链表称为单链表

结构定义

顺序存取

在顺序表中,想找第i个元素,可以立即通过L.elem[i−1]找到,想找哪个就找哪个,称为随机存取。但是在单链表中,想找第i个元素就没那么容易,必须从头开始,按顺序一个一个找,一直数到第i个元素,称为顺序存取

 

单链表的基本操作

初始化

单链表的初始化是指构建一个空表。先创建一个头节点,不存储数据,然后令其指针域为空,如图

插入(头插)

插入头节点的后面

头插法每次把新节点插入到头节点之后,创建的单链表和数据输入顺序相反

等号右侧是节点的地址,左侧是节点的指针域

① s->next=L->next:L->next存储的是下一个节点地址“9630”,将该地址赋值给s->next指针域,即s节点的指针指向1节点

② L->next=s:将s节点的地址“2046”赋值给L->next指针域,即节点的指针指向s节点

修改指针顺序?

为什么要先修改后面那个指针呢?

因为一旦修改了L节点的指针域指向s,那么原来L节点后面的节点就找不到了,因此修改指针是有顺序的。

原则:先修改没有指针标记的那一端

插入(尾插)

其创建的单链表和数据输入顺序一致

尾插法每次把新节点链接到链表的尾部,因此需要一个尾指针永远指向链表的尾节点

取值

查找

在一个单链表中查找是否存在元素e,可以定义一个p指针,指向第一个元素节点,比较p指向节点的数据域是否等于e。如果相等,查找成功,返回true;如果不等,则p指向的下一个节点,继续比较,如果为空,查找失败,返回false,如图

删除

删除一个节点,实际上是把这个节点跳过去。根据单向链表向后操作的特性,要想跳过第i个节点,就必须先找到第−1个节点,否则是无法跳过去的。

 

#include <iostream>
using namespace std;
//typedef 将结构体等价为类型名Lnode,指针Linklist 
typedef struct Lnode{
	int data;
	struct Lnode *next;//指向下一个节点的指针 
}Lnode,*LinkList; 
bool InitList(LinkList &L){//构建一个空的单链表L 
	L = new Lnode;//生成新节点作为头节点
	if(!L)return false;//生成失败
	L->next=NULL;//头节点的指针域置空
	return true; 
}
void CreateListT(LinkList &L){//头插法创建单链表 
	int n;//输入n个元素的值,建立到头节点的单链表L
	LinkList s;//定义一个指针变量 
	L = new Lnode;
	L->next=NULL;
	cout<<"输入n个元素"<<endl;
	cin >>n;
	cout<<"请依次输入n个元素"<<endl;
	cout<<"头插法创建单链表"<<endl;
	while(n--){
		s=new Lnode;//创建新节点
		cin >>s->data;
		s->next=L->next;
		L->next=s;//将节点s插入头节点后面 
	}
}
void CreateListW(LinkList &L){//尾插法创建单链表 
	int n;//输入n个元素的值,建立到头节点的单链表L
	LinkList s,r; 
	L = new Lnode;
	L->next=NULL;//先建立一个带头节点的空链表 
	r=L;//尾指针r指向头节点 
	cout<<"输入n个元素"<<endl;
	cin >>n;
	cout<<"请依次输入n个元素"<<endl;
	cout<<"尾插法创建单链表"<<endl;
	while(n--){
		s=new Lnode;//创建新节点
		cin >>s->data;
		s->next=NULL;
		r->next=s;//将节点s插入尾节点后面 
		r=s;//r指向新的尾节点 s 
	}
}
bool GetElem(LinkList L,int i,int &e){//单链表取值
	//在带头节点的单链表L中查找第i个元素,e记录第i个元素的值
 	int j;
 	LinkList p;
 	p=L->next;//定义p指针,指向第一个数据节点
 	j=1;//j为计数器
 	while(j<i&&p){//顺着链表向后扫描,直到p指向第i个元素或p为空 
 	p=p->next;//p指向下一个节点 
 	j++;
 	}	
 	if(!p||j<i)return false;//p为空,或j值<i 
 	e=p->data;//取第i个节点的数据域
 	return true; 
} 
bool LocateElem(LinkList L,int e){//在带头节点的单链表L中查找值为e的元素 
	LinkList p;
	p=L->next;//定义p指针,指向第一个数据节点
	while(p&&p->data!=e){//p不为空且未找到则继续 
		p=p->next;//指向下一节点 
	}
	if(!p)return false;//查找失败,p为NULL  
	return true;
}
bool InsertElem(LinkList &L,int i,int e){
	//在带头节点的单链表L中第i个位置之前插入值为e的新节点
	int j;
	LinkList p,s;
	p=L->next;//定义p指针,指向第一个数据节点 
	j=1;
	while(p&&j<i-1){//p不为空且j<i-1,则继续 
		p=p->next;
		j++;
	}
	if(!p||j>i-1)return false;//p为空,或j不满足 
	s = new Lnode;
	s->data=e;
	s->next=p->next;//类似于头插 
	p->next=s;
	return true; 
	 
} 
bool DeleteElem(LinkList &L,int i){
	//在带头节点的单链表L中,删除第i个位置
	int j;
	LinkList p,q;
	p=L->next;
	j=1; 
	while(p&&j<i-1){
		p=p->next;
		j++;
	}
	if(!p||j>i-1)return false;
	q=p->next;//临时保存被删除节点的地址已备释放空间(第i个节点) 
	p->next=q->next;
	delete q;//释放第i个节点的空间 
	return true; 
	
} 
void PrintElem(LinkList L){
	LinkList p;
	p=L->next;
	while(p){
		cout <<p->data<<" ";
		p=p->next;
	}
	cout <<endl;
}
int main()
{
	int i,x,e,choose;
	LinkList L;
	cout<<"1. 初始化\n";
	cout<<"2. 创建单链表(前插法)\n";
	cout<<"3. 创建单链表(尾插法)\n";
	cout<<"4. 取值\n";
	cout<<"5. 查找\n";
	cout<<"6. 插入\n";
	cout<<"7. 删除\n";
	cout<<"8. 输出\n";
	cout<<"0. 退出\n";
	choose=-1;
	while(choose!=0)
    {
		cout<<"请输入数字选择:";
		cin>>choose;
		switch(choose)
		{
			case 1: //初始化一个空的单链表
				if(InitList(L))
					cout<<"初始化一个空的单链表!\n";
				break;
			case 2: //创建单链表(前插法)
				CreateListT(L);
	            cout<<"头插法创建单链表输出结果:\n";
	            PrintElem(L);
				break;
	        case 3: //创建单链表(尾插法)
				CreateListW(L);
	            cout<<"尾插法创建单链表输出结果:\n";
	            PrintElem(L);
				break;
			case 4: //单链表的按序号取值
				cout<<"请输入一个位置i用来取值:";
				cin>>i;
				if(GetElem(L,i,e))
	            {
					cout<<"查找成功\n";
					cout<<"第"<<i<<"个元素是:"<<e<<endl;
				}
				else
					cout<<"查找失败\n\n";
				break;
			case 5: //单链表的按值查找
				cout<<"请输入所要查找元素x:";
				cin>>x;
				if(LocateElem(L,x))
					cout<<"查找成功\n";
				else
					cout<<"查找失败! "<<endl;
				break;
			case 6: //单链表的插入
				cout<<"请输入插入的位置和元素(用空格隔开):";
				cin>>i;
				cin>>x;
				if(InsertElem(L,i,x))
					cout<<"插入成功.\n\n";
				else
					cout<<"插入失败!\n\n";
				break;
			case 7: //单链表的删除
				cout<<"请输入所要删除的元素位置i:";
				cin>>i;
				if(DeleteElem(L, i))
					cout<<"删除成功!\n";
				else
					cout<<"删除失败!\n";
				break;
			case 8: //单链表的输出
				cout<<"当前单链表的数据元素分别为:\n";
				PrintElem(L);
				cout<<endl;
				break;
		}
	}
	return 0;
} 

双向链表

定义

单链表只能向后操作,不可以向前操作。为了向前、向后操作方便,可以给每个元素附加两个指针域,一个存储前一个元素的地址,另一个存储下一个元素的地址。这种链表称为双向链表

结构体定义

链表优缺点

优点:链表是动态存储,不需要预先分配最大空间;插入删除不需要移动元素。

缺点:每次动态分配一个节点,每个节点的地址是不连续的,需要有指针域记录下一个节点的地址,指针域需要占用一个int的空间,因此存储密度低(数据所占空间/节点所占总空间)。存取元素必须从头到尾按顺序查找,属于顺序存取。

双向链表的基本操作

创建(头插)

插入

单链表只有一个指针域,是向后操作的,不可以向前处理,因此单链表如果在第i个节点之前插入一个元素,就必须先找到第i−1个节点。在第i个节点之前插入一个元素相当于把新节点放在第i−1个节点之后。而双向链表不需要,因为有两个指针,可以向前后两个方向操作,直接找到第i个节点,就可以把新节点插入第i个节点之前。这里假设第i个节点是存在的,如果第i个节点不存在,而第i−1个节点存在,还是需要找到第i−1个节点,将新节点插入第−1个节点之后,如图

#include <iostream>
using namespace std;
typedef struct DuLnode{
	int data;
	struct DuLnode *prior,*next;
}DuLnode,*DuLinklist;
bool InitList(DuLinklist &L){//构建一个空的双向链表 
	L = new DuLnode;//生成新节点作为头节点,用头指针L指向头节点 
	if(!L)return false;
	L->prior=L->next=NULL;//头节点的两个指针域置空
	return true; 
}  
void CreateListT(DuLinklist &L){//头插法创建双向链表 
	//输入n个元素的值,建立到头结点的单链表L
	int n;
	DuLinklist s;//定义一个指针变量
	L=new DuLnode;
	L->prior=L->next=NULL;
	cout <<"请输入元素个数n: "<<endl;
	cin >>n;
	cout <<"请依次输入n个元素: "<<endl;
	cout <<" 头插法创建单链表"<<endl;
	while(n--){
		s=new DuLnode;//生成新节点
		cin >>s->data;
		if(L->next){
		//如果L后面有节点,则修改其后面节点的prior指针,否则只修改后面3个指针即可 
		L->next->prior=s;
		}
		s->next=L->next;
		s->prior=L;
		L->next=s; 
	}
	 
} 
bool InsertElem(DuLinklist &L,int i,int &e){
	int j=1;
	DuLinklist p,s;
	p=L->next;
	while(p&&j<i){
		p=p->next;
		j++;
	}
	if(!p||j>i)return false;
	s = new DuLnode;
	s->data=e;
	p->prior->next=s;
	s->prior=p->prior;
	s->next=p;
	p->prior=s;
	return true;
} 
bool DeleteElem(DuLinklist &L,int i){
	int j=1;
	DuLinklist p;
	p=L->next;
	while(p&&j<i){
		p=p->next;
		j++;
	}
	if(!p||j>i)return false;
	if(p->next){
	  p->next->prior=p->prior;
	}
	p->prior->next=p->next;
	delete p;//释放被删除节点的空间 
	return true;
}

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值