我们的目标:
1、了解线性结构的特点 掌握顺序表的定义、查找、插入和删除
2、掌握链表的定义、创建、查找、插入和删除
3、能够从时间和空间复杂度的角度比较两种存储结构的不同特点及其适用场合(持续更新)
一、补充
(为了以后学习理解更容易,这里给大家补充几个知识)
1.1 C语言的动态分配函数( <stdlib.h> )
(为了以后学习理解更容易,这里给大家补充几个知识)
malloc(m):开辟m字节长度的地址空间,并返回这段空间的首地址。
sizeof(x):计算变量x的长度。
free(p):释放指针p所指变量的存储空间,即彻底删除一个变量。
1.2 C++的动态存储分配
new 类型名T(初值列表)
功能:
申请用于存放T类型对象的内存空间,并依初值列表赋以初值。
结果值:
成功:T类型的指针,指向新分配的内存。
失败:0(NULL)
代码如下(示例):
int *p1= new int;
int *p1 = new int(10);
delete 指针p
功能:
释放指针P所指向的内存,p必须是new操作的返回值。
1.3 C++中的参数传递
函数调用时传送给形参表的实参必须与形参在类型、个数、顺序上保持一致
参数传递有两种方式 :
传值方式(参数为整型、实型、字符型等)
传地址 :参数为指针变量、 参数为引用类型 、参数为数组名。
下面我们来看传值方式:
传值方式就是把实参的值传送给函数局部工作区相应的副本中,函数使用这个副本执行必要的功能。函数修改的是副本的值,实参的值不变。
代码如下:
#include <iostream.h>
void swap(float m,float n)
{
float temp;
temp=m;
m=n;
n=temp;
}
void main()
{
float a,b;
cin>>a>>b;
swap(a,b);
cout<<a<<endl<<b<<endl;
}
传地址方式--指针变量作参数
形参变化影响实参。
代码如下:
#include <iostream.h>
void swap(float *m,float *n)
{
float t;
t=*m;
*m=*n;
*n=t;
}
void main()
{
float a,b,*p1,*p2;
cin>>a>>b;
p1=&a; p2=&b;
swap(p1, p2);
cout<<a<<endl<<b<<endl;
}
传地址方式--引用类型作参数
什么是引用??
通俗来讲,引用就是用来给一个对象提供一个替代的名字。
代码如下:
#include<iostream.h>
void main(){
int i=5;
int &j=i;
i=7;
cout<<"i="<<i<<" j="<<j;
}
我们可以看到,j是一个引用类型, 代表i的一个替代名。 i值改变时,j值也跟着改变,所以会输出。 最后输出为:i=7 j=7
代码如下:
#include <iostream.h>
void swap(float& m,float& n)
{
float temp;
temp=m;
m=n;
n=temp;
}
void main()
{
float a,b;
cin>>a>>b;
swap(a,b);
cout<<a<<endl<<b<<endl;
}
总结:引用类型作形参的三点说明
1. 传递引用给函数与传递指针的效果是一样的,形参变化实参也发生变化。
2. 引用类型作形参,在内存中并没有产生实参的副本,它直接对实参操作;而一般变量作参数,形参与实参就占用不同的存储单元,所以形参变量的值是实参变量的副本。因此,当参数传递的数据量较大时,用引用比用一般变量传递参数的时间和空间效率都好。
3.指针参数虽然也能达到与使用引用的效果,但在被调函数中需要重复使用“*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。
传地址方式--数组名作参数
传递的是数组的首地址,对形参数组所做的任何改变都将反映到实参数组中。
代码如下:
#include<iostream.h>
void sub(char);
void main(void )
{ char a[10]=“hello”;
sub(a);
cout<<a<<endl;
}
void sub(char b[ ])
{ b[ ]=“world”;}
这里举个例子,用数组作函数的参数,求10个整数的最大数。
代码如下:
#include <iostream.h>
#define N 10
int max(int a[]);
void main ( ) {
int a[10];
int i,m;
for(i=0;i<N;i++)
cin>>a[i];
m=max(a);
cout<<"the max number is:"<<m;
}
int max(int b[]){
int i,n;
n=b[0];
for(i=1;i<N;i++)
if(n<b[i]) n=b[i];
return n;
}
进行练习
用数组作为函数的参数,将数组中n个整数按相反的顺序存放,要求输入和输出在主函数中完成
代码如下:
#include <iostream.h>
#define N 10
void sub(int b[ ]){
int i,j,temp,m;
m=N/2;
for(i=0;i<m;i++){
j=N-1-i;
temp=b[i];
b[i]= b[j];
b[j]=temp; }
return ;}
void main ( ) {
int a[10],i;
for(i=0;i<N;i++)
cin>>a[i];
sub(a);
for(i=0;i<N;i++)
cout<<a[i];
}
二、线性表的重要基本操作
初始化 、 取值 、 查找 、 插入 、 删除。
2.1 初始化
2.1.1初始化线性表L (参数用引用)
代码如下:
Status InitList_Sq(SqList &L) //构造一 个空的顺序表L
{
L.elem=new ElemType[MAXSIZE]; //为顺序表分配空间
if(!L.elem) exit(OVERFLOW); //存储分配失败
L.length=0; //空表长度为0
return OK;
}
2.1.2初始化线性表L (参数用指针)
代码如下:
Status InitList_Sq(SqList *L) //构造一个空的顺序表L
{
L-> elem=new ElemType[MAXSIZE]; //为顺序表分配空间
if(! L-> elem) exit(OVERFLOW); //存储分配失败
L-> length=0; //空表长度为0
return OK;
}
这里进行一些补充
几个简单基本操作的算法实现
1. 销毁线性表L
代码如下:
void DestroyList(SqList &L)
{
if (L.elem) delete[]L.elem;
//释放存储空间
}
2、清空线性表L
void ClearList(SqList &L)
{
L.length=0;
//将线性表的长度置为0
}
2.2 取值
根据位置i获取相应位置数据元素的内容
举例:获取线性表L中的某个数据元素的内容。
代码如下:
int GetElem(SqList L,int i,ElemType &e)
{
if (i<1||i>L.length) return ERROR;
//判断i值是否合理,若不合理,返回ERROR
e=L.elem[i-1]; //第i-1的单元存储着第i个数据
return OK;
}
e=L.elem[i-1]; 这里属于随机存取。
2.3 查找
根据指定数据获取数据所在的位置
顺序查找图示:
在线性表L中查找值为e的数据元素;
代码如下:
int LocateELem(SqList L,ElemType e)
{
for (i=0;i< L.length;i++)
if (L.elem[i]==e) return i+1;
return 0;
}
插入(插在第 i 个结点之前) ;
0+1+2+……n 插第 4 个结点之前,移动 6-4+1 次 插在第 i 个结点之前,移动 n-i+1 次 ;
算法步骤
1.判断插入位置i 是否合法。
2.判断顺序表的存储空间是否已满。
3.将第n至第i 位的元素依次向后移动一个位置,空出第i个位置。
4.将要插入的新元素e放入第i个位置。
5.表长加1,插入成功返回OK。
算法描述
在线性表L中第i个数据元素之前插入数据元素e。
代码如下:
Status ListInsert_Sq(SqList &L,int i ,ElemType e)
{
if(i<1 || i>L.length+1) return ERROR; //i值不合法
if(L.length==MAXSIZE) return ERROR; //当前存储空间已满
for(j=L.length-1;j>=i-1;j--)
L.elem[j+1]=L.elem[j]; //插入位置及之后的元素后移
L.elem[i-1]=e; //将新元素e放入第i个位置
++L.length; //表长增1
return OK;
}
算法分析
算法时间主要耗费在移动元素的操作上:
若插入在尾结点之后,则根本无需移动(特别快);
若元素全部后移(特别慢);
若要考虑在各种位置插入(共n+1种可能)的平均移动次数,用AMN来计算。
2.4 删除(删除第 i 个结点)
0+1+2+… n-1 删除第 4 个结点,移动 6-4 次 删除第 i 个结点,移动 n-i 次。
算法步骤
(1)判断删除位置i 是否合法(合法值为1≤i≤n)。
(2)将欲删除的元素保留在e中。
(3)将第i+1至第n 位的元素依次向前移动一个位置。
(4)表长减1,删除成功返回OK。
算法描述
将线性表L中第i个数据元素删除
代码如下:
Status ListDelete_Sq(SqList &L,int i)
{
if((i<1)||(i>L.length)) return ERROR; //i值不合法
for (j=i;j<=L.length-1;j++)
L.elem[j-1]=L.elem[j]; //被删除元素之后的元素前移
--L.length; //表长减1
return OK;
}
算法分析
算法时间主要耗费在移动元素的操作上:
若删除尾结点,则根本无需移动(特别快);
若删除首结点,则表中n-1个元素全部前移(特别慢);
若要考虑在各种位置删除(共n种可能)的平均移动次数,用AMN来计算。
三、总结
查找、插入、删除算法的平均时间复杂度为:O(n)
显然,顺序表的空间复杂度S(n)=O(1) (没有占用辅助空间)
顺序表(顺序存储结构)的特点:
1. 利用数据元素的存储位置表示线性表中相邻数据元素之间的前后关系,即线性表的逻辑结构与存储结构一致。
2. 在访问线性表时,可以快速地计算出任何一个数据元素的存储地址。因此可以粗略地认为,访问每个元素所花时间相等。
这种存取元素的方法被称为随机存取法
顺序表的优缺点:
优点:
时间:可以随机存取表中任一元素 。
空间:存储密度大(结点本身所占存储量 / 结点结构所占存储量)。
缺点:
时间:在插入、删除某一元素时,需要移动大量元素 。
空间:浪费存储空间,属于静态存储形式,数据元素的个数不能自由扩充。
以上就是今天要讲的内容,我们完成了第二个目标,基本掌握链表的定义、创建、查找、插入和删除。我会持续更新的,希望大家多多支持!一起加油!