数据结构(上)
Introduce
关于数据结构(data structure)
数据元素
结构(structure):数据元素的集合中,元素相互之间的关系
逻辑结构
集合,线性结构,树型结构,图状结构
物理结构
顺序,链接,索引,散列
数据结构是一个二元组(表示数据元素与数据关系之间的二元组)
D是数据元素的有限集合
S是D上关系的有限集合
关于算法:
有关算法的表示方法:
有穷性,确定性,可行性,输入,输出(五大特性)
可读性,健壮性,高效率与低存储量,正确性
时间复杂度:o(t(n))表示算法的时间复杂度,t(n)是算法的有关n的数量级,一般是算法中频度最大的语句频度,通常是最深层循环内的原操作执行的次数
线性结构
线性表
线性表就是用来存储线性数据的一种数据结构。
顺序表
顺序表即通过数组来实现的线性表,缺点是:顺序表的数据有限,删除与插入功能比较受局限,需要移动后面的元素,插入和删除一般需要移动n/2个元素。
优点是顺序表的访问元素的局限比较小。
下面是顺序表实现线性表并且通过线性表实现多项式相加的功能:
//数组实现线性表
#include <iostream>
#define maxsize 100
using namespace std;
struct ElemType
{
float coef;
int expo;
} ;
struct Sqlist
{
ElemType *p;
int length;
int Maxsize;
};
bool InitList_Sq(Sqlist &L)
{
L.p=new ElemType[maxsize] ;
if(!L.p)return 0;
L.length=0;
L.Maxsize=maxsize;
return 1;
}
bool MergeList_Sq(Sqlist &La,Sqlist &Lb,Sqlist &Lc)
{
int m=0,n=0;
for(;m<La.length&&n<Lb.length;Lc.length++)
{
if(La.p[m].expo<Lb.p[n].expo){
Lc.p[Lc.length]=Lb.p[n];n++;
}
else{
if(La.p[m].expo>Lb.p[n].expo){
Lc.p[Lc.length]=La.p[m];m++;
}
//这里注意:在某个if else语句执行的时候比多个if语句更可靠
else{
Lc.p[Lc.length].coef=La.p[m].coef+Lb.p[n].coef;
Lc.p[Lc.length].expo=La.p[m].expo;
m++;n++;
}}
}
if(m==La.length&&n==Lb.length)
{
return 1;
}
if(m==La.length)for(;n<Lb.length;n++)
{
Lc.p[Lc.length]=Lb.p[n];
Lc.length++;
}
if(n==Lb.length)for(;m<La.length;m++)
{
Lc.p[Lc.length]=La.p[m];
Lc.length++;
}
return 1;
}
void displaylist (Sqlist &L)
{
for(int i=0;i<L.length;i++)
{
if(i==0)cout<<L.p[i].coef<<"x^"<<L.p[i].expo;
else cout<<"+"<<L.p[i].coef<<"x^"<<L.p[i].expo;
}
cout<<"\n";
}
void insert (Sqlist &L,int m,ElemType e)
{
for(int i=L.length;i>m;i--)L.p[i]=L.p[i-1];
L.p[m]=e;
L.length++;
}
bool pushback(Sqlist &L,ElemType e)
{
if(L.length==L.Maxsize)return 0;
L.p[L.length]=e;
L.length++;
return 1;
}
void putin(Sqlist &L)
{
cout<<"您输入多项式的项数:";
int t;
cin>>t;
int m;
float n;
ElemType e;
cout<<"请按照降幂输入您的多项式:";
for(int i=0;i<t;i++)
{
cout<<"请输入第"<<i+1<<"项的系数";
cin>>n;
cout<<"请输入第"<<i+1<<"项的指数";
cin>> m;
e.coef=n;e.expo=m;
pushback(L,e);
}
}
void distorylist (Sqlist &L)
{
delete []L.p;
}
int main()
{
Sqlist La,Lb,Lc;
InitList_Sq(La);
InitList_Sq(Lb);
InitList_Sq(Lc);
cout<<"输入第一个多项式\n";
putin(La);
cout<<"输入第二个多项式\n";
putin(Lb);
cout<<"\n\n";
cout<<"第一项多项式为:"<<endl;
displaylist(La);
cout<<"第二项多项式为:"<<endl;
displaylist(Lb);
MergeList_Sq(La,Lb,Lc);
cout<<"加和后的多项式为:";
displaylist(Lc);
distorylist(La);distorylist(Lb);
distorylist(Lc);
}
链表实现线性表
通过链表实现线性表,就是一种通过节点链接形成的线性表,这种线性表优点是删除和添加功能比较容易实现,但是查找工作比较困难实现。(注意:这里很容易考察他的添加以及删除操作)
下面是通过链表实现实例:
//链表实现线性表
#include"stdio.h"
#include"stdlib.h"
#include <iostream>
using namespace std;
struct ElemType
{
float coef;
int expo;
} ;
typedef struct Lnode
{
ElemType e;
Lnode *next;
} *linklist;
bool pushback(linklist L,ElemType w)
{
//在函数里new的不会被销毁,但是他的指针会被销毁
//在函数里创建的东西除了new出来的都会被销毁
Lnode *p,*q;
p=L;
while (p->next!=NULL)
{
p=p->next;
}
q=new Lnode;
q->e=w;
p->next=q;
q->next=NULL;
return 1;
}
/* struct Lnode *creat() //定义建立多项式函数,并返回链表头指针
{
struct Lnode *h=NULL,*q,*p; //定义结构体头指针h,标记指针p和q
int i=1,N; //定义多项式的项数N及标记数i
printf("请输入多项式的项数:");
scanf("%d",&N);
for(;i<=N;i++)
{p=new Lnode; //为下一个节点分配空间
printf("请输入第%d项的系数:",i);
scanf("%f",&p->e.coef);
fflush(stdin);
printf("请输入第%d项的指数:",i);
scanf("%d",&p->e.expo);
fflush(stdin);
if(i==1) {h=p;q=p;} //建立头节点
else
{
q->next=p;
q=p;
}
}
q->next=NULL;
p->next=NULL;
return h;
}
*/ linklist creatlist()
{
linklist L=new Lnode;
L->next=NULL;
int n;
cout<<"请输入多项式的项数:";
cin>>n;
cout<<"请按照降幂输入多项式:";
ElemType y;
for(int i=0;i<n;i++)
{
cout<<"第"<<i+1<<"项的系数是:";
cin>>y.coef;
cout<< "第"<<i+1<<"项的指数是:";
cin>>y.expo;
pushback(L,y);
}
return L;
}
bool deleteelem(linklist L,int n)
{
//删除第n个元素
Lnode *p,*q;
p=L;
for(int i=0;i<n;i++)
{
p=p->next;
}//把指针调到第n个元素的前一个元素
q=p;
p=p->next;
q->next=p->next;
delete p;
return 1;
}
void displaylist(linklist L)
{
Lnode *p=L;int i=0;
while(p->next)
{
i++;p=p->next;
if(i==1)cout<<p->e.coef<<"x^"<<p->e.expo;
else cout<<"+"<<p->e.coef<<"x^"<<p->e.expo;
}
}
struct Lnode *add(struct Lnode *h1,struct Lnode *h2) //定义加法函数,并返回结果的链表的头指针
{
struct Lnode *p,*q,*head,*r; //定义结构体头指针head和标记指针p,q,r
p=h1;
q=h2;
p=p->next;
q=q->next;
head=new Lnode; //为结果多项式分配头指针
if(p->e.expo>=q->e.expo) {head->next=p;p=p->next;}
else
{
if(p->e.expo<q->e.expo) {head->next=q;q=q->next;}
else {p->e.coef=p->e.coef+q->e.coef;head->next=p;p=p->next;q=q->next;}
}//这里是带有头节点的链表,需要跳过头节点考虑问题
r=head->next;
while(p!=NULL&&q!=NULL)
{
if(p->e.expo>q->e.expo) {r->next=p;p=p->next;}
else
{
if(p->e.expo<q->e.expo) {r->next=q;q=q->next;}
else {p->e.coef=p->e.coef+q->e.coef;r->next=p;p=p->next;q=q->next;}
}
r=r->next;
}
if(p==NULL&&q==NULL) r->next=NULL;
else
{
if(p==NULL) r->next=q;
if(q==NULL) r->next=p;
}
return head;
}
int main()
{
Lnode *q,*p;
p=creatlist();
q=creatlist();
cout<<"输入的第一个多项式为:";
displaylist(p);
cout<<"输入的第二个多项式为;";
displaylist(q);
linklist c=add(q,p);
cout<<endl;
cout<<"加和得到的多项式为 :"<<endl;
displaylist(c);
}
关于多项式相加的分析:
多项式相加通过添加两个指针,分别指向两个链表,实现两个多项式的分别比较,分为三种情况。两种实现都是基于多项式的基本操作,如线性表的添加元素,线性表的删除元素。如果两个元素的指数相等就相加,注意这种连续比较的算法最后要通过接上没有比较完的线性表到最终线性表的末尾!
附加:关于头结点,循环链表,双向链表
链表里面有一个头结点,头结点主要作用就是将其他操作避免考虑第一个节点的特殊情况(如果不添加,那么在删除到只有一个元素的时候还要考虑到底是还有几个元素,但是添加之后,就不用考虑直接删除头结点后面那个要元素就行了!),创建一个头结点,里面的数据域不放数据,只在next域中存放链表第一个结点的地址。当然头结点也可以用来存储一些非必要数据,比如有几个节点。对于循环链表,最大的特点就是通过尾节点指向头结点,而双向链表也是一种特殊的链表,双向链表的一些操作有些不同,比如可以通过后面的节点删除或者插入前面的节点。
其他线性结构:
栈结构:
栈结构最主要的特点就是栈结构的先进后出(first in last out)的特点,最后栈结构主要在实际应用中处理需要倒序处理的数据。比如实现对于十进制转化为二进制。
同样的栈结构也有很多操作,比如入栈以及出栈操作
对于栈结构也有一个实例实现:实现对于多项式的运算:
#include <iostream>
#include <bits/stdc++.h>
#define maxsize 100
using namespace std;
struct ElemType
{
int num;
char oper;
} ;
struct Stack
{
ElemType *top,*base;
//栈顶指针指向栈的第一个元素的前面
int Smaxsize;
};
bool InitStack(Stack &L)
{
L.base=new ElemType[maxsize];
L.top=L.base;
return 1;
}
bool Is_empty(Stack L)
{
if(L.base==L.top)return 1;
else return 0;
}
ElemType Gettop(Stack L)
{
return L.top[-1];
}
ElemType pop(Stack &L)
{
ElemType q=L.top[-1];
L.top--;
// L.top=&L.base[Getlength(L)-2];
return q;
}
bool push(Stack &L,int t)
{
L.top->num=t;
L.top++;
return 1;
}
bool push(Stack &L,char t)
//在栈顶插入元素
{
L.top->oper=t;
L.top++;
return 1;
}
int percede(char theta1,char theta2)//比较两个字符串的优先级,第一个是栈内的
{
//赋给他们各自优先值
int a[7]={5,5,7,7,1,2,0};//栈内优先级
int b[7]={4,4,6,6,8,1,0};//栈外优先级
char c[7]={'+','-','*','/','(',')','#'};
int i,j;
for(i=0;i<7;i++)
{
if(theta1==c[i])break;
}
for(j=0;j<7;j++)
{
if(theta2==c[j])break;
}
if(a[i]<b[j])return 1;//栈内比站外优先级小
if(a[i]>b[j])return -1;//栈内比栈外优先级大
return 0;
}
int caculate(int n,char operate,int m)
{
//计算一个表达式的值
switch(operate)
{
case '-':return m-n;break;
case '+':return n+m;break;
case '*':return n*m;break;
case '/':return m/n;break;
default:cout<<"The operate is fault!";return 0;
}
}
bool In(char n)
{
//判断这个字符是不是数字
if(n>='0'&&n<='9')
{
return 1;
}
else return 0;
}
double EvaluateExpression(char *p)
{
//计算表达式的值,其中表达式以#开头以#结束
Stack optr,opnd;
InitStack(optr);
InitStack(opnd);
push(optr,'#');
//完成创建一个栈的工作
for (int i=0;;i++)
{
bool y=In(p[i]);
flag: if(y)
{//存入数据
if(In(p[i+1]))
{
push(opnd,10*(int(p[i])-48)+int(p[++i])-48);
}
else push(opnd,int(p[i])-48);
}
//将他们都放入存char的ElemType中
else{
//运算符的时候
char w=Gettop(optr).oper;
switch(percede(w,p[i]))
{
//栈外优先级高
case 1: {push(optr,p[i]);break;}//注意这里的1与‘1’的区别
//栈内优先级高
case -1:{
int a,b,d;
char c;
a=pop(opnd).num;
b=pop(opnd).num;
c=pop(optr).oper;
d=caculate(a,c,b);
push(opnd,d);
goto flag;
break;
}break;
//两个优先级相等
case 0:{ pop(optr);
break;
}
}
if (p[i]=='#')break;
}
}
return Gettop(opnd).num;
}
int main ()
{
//这里将一个输入字符存入数组是一个知识点
char a[100];
cout<<"请输入表达式:";
memset(a,100,'0');
gets(a);
for(int i=0;i<100;i++)
{
if(a[i]=='\0')
{
a[i]='#';
break;
}
}
cout<<EvaluateExpression(a);
}
这里的多项式运算的算法主要是通过两个栈,一个数字栈,每当栈内的优先级高于栈外的运算符的优先级的时候就要出来两个数字栈的元素进行运算,算出一个数字之后在运算。当栈内的优先级小于的时候就将栈外的运算符入栈,等待下一个运算符的进入。等于的时候就可以抵消这两个运算符。(前提是在两端加上两个优先级最小的运算符)
队列结构:
栈结构最主要的特点就是队列结构的先进后出(first in first out)的特点,最后栈结构主要在实际应用中处理需要倒序处理的数据。比如实现基数排序算法就可以用到队列结构。
对于队列(链表实现)的操作,出列操作,注意要先判断是否是最后一个节点(rearhead)。
对于队列的数组存储(循环队列):
循环队列可以是尾指针指向最后一个元素的下一个元素,也可以是第一个元素指向上一个元素,这里的循环队列里的尾指针指向的元素要是要向下一个移动的话就是(rear+1)%Max。同样的头指针指向指向的元素的位置向下一个移动也是(head+1)%Max。
但是,这样的循环队列就会少用一个元素。
另外,对于循环队列的溢出以及为空的时候,循环队列的判断里面的都是直接front和rear的指针,而不是原来的位置关系。对于循环队列以及链式队列的为空的判断就是直接尾指针等于头指针就可以了。
出队列伪算法:
public int OutQueue(){
if(front rear) return 0;
else{
node *q = front->next;
front->next = q->next;
data x = q->data;
if(q== rear) rear = front; //当链表中只有一个结点时
free(q) ;
return x;
}
}
关于后面的线性结构:
线性结构还有串结构,面试和考研的时候最主要的就是对于串结构里面KMP算法的考察。