第一节.数据和算法
常用时间复杂度大小关系
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)
int sum=0;
for(int i=0;i<=n;i=2*i){
sum=sum+i;
}
满足条件:2k<n => k<log2n
时间复杂度 O(logn) 这里时间复杂度不考虑底数2
第二节.线性表
线性表是指具有相同数据类型的n个元素的有限序列
线性表的顺序存储
1.顺序表的存储方式
1.利用一组地址连续的存储单元,一次存储线性表中的数据元素,顺序存储的线性表也叫作顺序表
2.loc(an)=loc(a1)+(n-1)*d 知道首元素的存储地址和每个数据的存储单元就可以知道顺序表中的每一个数据的存储位置,时间复杂度为O(1),即为随机存取
#define maxsize 100//静态
typedef int elemtype
typedef struct{
elemtype data[maxsize];
int length;
}Sqlsit;
typedef int elemtype;//动态
#define initsize 100
typedef struct{
elemtype *data;//创建一个指针域
int maxsize,length;
}Seqlist;
Seqlist L;
L.data=(elemtype)malloc(sizeof(elemtype)*initsize);
//按照给定的需要的initsize大小动态的分配空间给顺序表
//这里要注意:链表和这个动态分配的顺序表的区别
*** 动态分配并不是链式存储,同样还是属于顺序存储空间,只是分配的空间大小可以在运行时决定
2.C动态分配空间知识复习
double *p;
p=(double*)malloc(30*sizeof(double));
1.首先开辟了30个double类型的空间,然后把p指向这个空间的位置。在这里的指针是指向第一个double值并非我们所有开辟的30个double的空间,这就和数组一样,指向数组的指针式指向数组首元素的地址,并非整个数组的元素。所以,在这里我们的操作也和数组是一样的, p[0]就是第一个元素,p[2]就是第二个元素。
2.当我们使用malloc()开辟完内存空间以后,我们所要考虑的就是释放内存空间,在这里,C给我们提供了free()函数。free()的參数就是malloc()函数所返回的地址,释放先前malloc()函数所开辟的空间,free函数也是必不可缺少的函数。
3.顺序表的基本操作
1.线性表的插入:在 1<= i <=length+1 的位置都可以进行插入操作,如果 i 的输入不合法则返回 false,否则将顺序表的第 i 个元素及其以后的元素都向右移动一个位置,腾出位置给新插入的元素,表长加1,插入成功,返回 true
bool listinsert(sqlsit &L,int i,elemtype e){
if(i<1||i>L.length+1||L.length>=maxsize)
return fasle;
for(int j=L.length;j>=i;j--)
L.data[j+1]=L.data[j];//定义表的第一个元素存储在数组data第一个空间
L.data[i]=e;
L.length++;
return true;
}
顺序表插入的算法分析:
最好情况:直接在表尾进行插入,数据不需要移动,时间复杂度为O(1)
最坏情况:在表头进行插入,数据需要移动 n次,时间复杂度为O(n)
平均情况:概率为从插入第一个位置到插入最后的n+1个位置 p = 1/(n+1)
对应着平均移动次数 n- i+1,将概率和平均移动次数相乘,从i=1到i=n+1进行求和,最后为 n/2,时间复杂度为O(n)
bool sqlistdelete(sqlist &L,int i,elemtype &e)
{
if(i<1||i>L.length)
return false;
e=L.data[i];
for(int j=i;j<=L.length;j++)
L.data[j]=L.data[j+1];
return true;
}
顺序表删除的算法分析:
最好情况:直接在表尾进行删除,数据不需要移动,时间复杂度为O(1)
最坏情况:在表头进行删除,数据需要移动 n-1 次,时间复杂度为O(n)
平均情况:概率为从插入第一个位置到插入最后 n 的位置 p = 1/n
对应着平均移动次数 n- i,将概率和平均移动次数相乘,从i=1到i=n+1进行求和,最后为 (n-1)/2,时间复杂度为O(n)
顺序表的优缺点分析
优点 | 缺点 |
---|---|
存储密度大,无需给存储数据关系增添额外的逻辑结构 | 删除和插入需要移动大量的数据元素 |
随机存取,想在哪里存数据或者去数据均可 | 对存储空间要求高,容易产生空间碎片 |
线性表的链式存储
1.单链表的存储方式
1.用一组任意的存储单元来存储这些数据信息,为了建立起来线性存储关系,每一个链表的结点不仅存放自身的数据信息,还存放了下一个数据信息的地址,这种方式称之为单链表
typedef struct Lnode{
elemtype data;
struct Lnode *next;
}Lnode,*linklist;
//该结构体命名为Lnode,指向这个结构体的指针类型称之为*linklist
2.通常用头指针来表示一个单链表,知道了头指针就知道了第一个结点的信息,这整条单链表也都知道了,linklist L,L则为这个单链表的头指针
3.在单链表的第一个结点前面附加一个一个不存储任何信息的空节点,称之为头结点,设立头结点可以处理操作方便,删除第一个元素结点或者在第一元素结点的后面增加结点都和其他的结点的操作一致,无论链表是否为空,头指针是指向头结点的非空指针。
头指针L(指向第一个结点的地址) —> 空的头结点 —> 带有存储信息的结点
不管带不带头结点,头指针始终指向链表的第一个结点,尾指针始终指向链表的最后一个节点,而头结点是带头结点链表中的第一个结点,结点内不存储信息,linklist L,则头指针L就代表一个单链表,通过头指针就能找到头结点
4.如何获取单项链表的头结点
A.声明链表后,马上定义一个头指针,指向链表的头结点,这样,不管程序运行到哪儿,都可以通过访问头指针来得到头结点。
B.定义单独的头结点,不赋值,永远只作为类似标志的性质,以后通过它访问整个链表,即链表第一个结点为头结点->Next
C.将单向链表制作为循环链表,在头结点设置特殊值,永远往一个方向找,一旦找到特殊值,即为找到头结点。
2.单链表的基本操作
1.创建单链表之头插法
linklist creatlinklist(linklist &L){
int x;
linklist s;
scanf("%d",&x);
L=(linklist)malloc(sizeof(Lnode));
while(x!=9999){
s=(linklist)malloc(sizeof(Lnode));
s.data=x;
s->next=L->next;//让s后面与头结点后面的结点相连
L->next=s;//让s连接在头结点后面
scanf("%d",&x);
}
return L;
}
新的结点都是插在头结点后面,生成的链表数据顺序与输入的数据顺序相反
创建结点指针后,linklist p,如果涉及data数值的改变
必须先向空间申请内存
p=(linklist)malloc(sizeof(Lnode));
如果仅仅只作为一个指针前后移动的话
则无需向空间申请内存
2.创建单链表之尾插法
linklist creatlinklist(linklist &L){
int x;
scanf("%d",&x);
L=(linklist)malloc(sizeof(Lnode));
linklist s,r=L;//r为表尾元素,s为新结点
while(x!=9999){
s=(linklist)malloc(sizeof(Lnode));
s.data=x;
r->next=s;//表尾元素的下一个值为s
r=s;//再移动表尾元素
scanf("%d",&x);
}
r->next=NULL;//保证最后的表尾元素下一值为空
return L;
}
尾插法读入数据的顺序和输入数据的顺序一致
3.查找单链表之按序号查找
linklist searchlinlist(linklist L,int i){
int j=1;
if(i==0)return L;
if(i<1)return NULL;
linklist p=L->next;//p从头结点的下一位结点开始
while(j<i&&p!=NULL){
p=p->next;
j++;
}
return p;
}
在这里默认头结点为i=0的结点
4.查找单链表之按值查找
linklist searchlinlist(linklist L,elemtype e){
linklist p=L->next;//p从头结点的下一位结点开始
while(p!=NULL&&p.data!=e){
p=p->next;
}
return p;
//要不p=NULL跳出要不p.data=e跳出,直接返回p就行
}
5.插入单链表的结点
bool insertlinlist(linklist &L,elemtype e,int i){
linklist r,s;
r=searchlinlist(L,i-1);//找到i-1的结点的位置
if(r==NULL)return false;
s=(linklist)malloc(sizeof(Lnode));
s.data=e;
s->next=r->next;
r->next=s;
return true;
}
6.删除单链表的结点
bool deletelinlist(linklist &L,int i){
linklist r,s;
r=searchlinlist(L,i-1);//找到i-1的结点的位置
if(r==NULL)return false;
s=r->next;//找到i的结点的位置
r->next=s->next;
free(s);
return true;
}
3.双链表
1.双链表的存储方式
typedef struct Dnode{
elemtype data;
struct Dnode *next,*prior;
}Dnode,*Dinklist;
相对于单链表而言,双链表就是在单链表的基础上面增加一个前驱指针,可以通过前驱指针找到前面的结点,典型的消耗空间换取时间的例子
2.双链表的按序号取值
linklist searchDinlist(Dinklist L,int i){
int j=1;
if