是一门研究非数值计算的程序设计问题中计算机的操作对象以及他们之间的关系和操作等的学科。
数据:对客观事物的符号表示,在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称。
数据元素:数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。
数据对象:性质相同的数据元素的集合,是数据的一个子集。
数据结构:相互之间存在一种或多种特定关系的数据元素的集合。
数据类型:一个值的集合和定义在这个值集上的一组操作的总称。
1)算法是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或多个操作。
算法五个特性:有穷性,确定性,可行性,输入,输出。
2)算法设计要求:正确性,可读性,健壮性,效率与低存储量需求。
3)算法分析:时间复杂度,空间复杂度,稳定性
在数据元素的非空有限集合中,(1)存在唯一的一个被称做“第一个”的数据元素;(2)存在唯一的一个被称做“最后一个”的数据元素;(3)除第一个之外,集合中的每个数据元素均只有一个前驱;(4)除最后一个之外,集合中每个数据元素均只有一个后继。
顺序存储结构和链式存储结构
顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。
通常用数组来描述数据结构中的顺序存储结构。
链式存储结构: 其结点在存储器中的位置是随意的,即逻辑上相邻的数据元素在物理上不一定相邻。通过指针来实现。
数据结构的基本运算:修改、插入、删除、查找、排序
1)修改:通过数组的下标便可访问某个特定元素并修改。
时间复杂度O(1)
2) 插入:在线性表的第i个位置前插入一个元素
实现步骤:
①将第n至第i 位的元素逐一向后移动一个位置;
②将要插入的元素写到第i个位置;
③表长加1。
注意:事先应判断: 插入位置i 是否合法?表是否已满?
应当符合条件: 1≤i≤n+1 或 i=[1, n+1]
核心语句:
for (j=n; j>=i; j--)
a[j+1]=a[ j ];
a[ i ]=x;
n++;
插入时的平均移动次数为:n(n+1)/2÷(n+1)=n/2≈O(n)
3)删除:删除线性表的第i个位置上的元素
实现步骤:
①将第i+1 至第n 位的元素向前移动一个位置;
②表长减1。
注意:事先需要判断,删除位置i 是否合法?
应当符合条件:1≤i≤n 或 i=[1, n]
核心语句:
{
for ( j=i+1; j<=n; j++ )
a[j-1]=a[j];
n--;
}
顺序表删除一元素的时间效率为:T(n)=(n-1)/2 ≈O(n)
顺序表插入、删除算法的平均空间复杂度为O(1)
线性链表:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。
一个数据元素称为一个结点,包括两个域:存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。指针域中存储的信息称作指针或链。
由于链表的每个结点中只包含一个指针域,故线性链表又称为单链表。
1)单链表的修改(或读取)
思路:要修改第i个数据元素,必须从头指针起一直找到该结点的指针p,return p;
然后才能:p->data=new_value
读取第i个数据元素的核心语句是:
Linklist *find(Linklist *head ,int i)
{
int j=1;
Linklist *p;
P=head->next;
While((p!=NULL)&&(j<i))
{
p=p->next;
j++;
}
}
链表插入的核心语句:
Step 1:s->next=p->next;
Step 2:p->next=s;
3)单链表的删除
删除动作的核心语句(要借助辅助指针变量q):
q = p->next; //首先保存b的指针,靠它才能找到c;
p->next=q->next; //将a、c两结点相连,淘汰b结点;
free(q) ; //彻底释放b结点空间
设p已指向第i 元素,请在第 i 元素前插入元素 x:
① ai-1的后继从 ai ( 指针是p)变为 x(指针是s) :
s->next = p ; p->prior->next = s ;
② ai 的前驱从ai-1 ( 指针是p->prior)变为 x ( 指针是s);
s->prior = p ->prior ; p->prior = s ;
设p指向第i 个元素,删除第 i 个 元素
后继方向:ai-1的后继由ai ( 指针p)变为ai+1(指针 p ->next );
p ->prior->next = p->next ;
前驱方向:ai+1的前驱由ai ( 指针p)变为ai-1 (指针 p -> prior );
p->next->prior = p ->prior ;
循环链表是另一种形式的链式存储结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
循环链表的操作和线性链表基本一致,差别仅在于算法中的循环条件不是p或p->next是否为空,而是它们是否等于头指针。
学习重点:
线性表的逻辑结构,指线性表的数据元素间存在着线性关系。在顺序存储结构中,元素存储的先后位置反映出这种线性关系,而在链式存储结构中,是靠指针来反映这种关系的。
顺序存储结构用一维数组表示,给定下标,可以存取相应元素,属于随机存取的存储结构。
链表操作中应注意不要使链意外“断开”。因此,若在某结点前插入一个元素,或删除某元素,必须知道该元素的前驱结点的指针。
掌握通过画出结点图来进行链表(单链表、循环链表等)的生成、插入、删除、遍历等操作。
数组(主要是二维)在以行序/列序为主的存储中的地址计算方法。
补充重点:
每个存储结点都包含两部分:数据域和指针域(链域)
在单链表中,除了首元结点外,任一结点的存储位置由 其直接前驱结点的链域的值 指示。
在链表中设置头结点有什么好处?
头结点即在链表的首元结点之前附设的一个结点,该结点的数据域可以为空,也可存放表长度等附加信息,其作用是为了对链表进行操作时,可以对空表、非空表的情况以及对首元结点进行统一处理,编程更方便。
如何表示空表?
(1)无头结点时,当头指针的值为空时表示空表;
(2)有头结点时,当头结点的指针域为空时表示空表。
链表的数据元素有两个域,不再是简单数据类型,编程时该如何表示?
因每个结点至少有两个分量,且数据类型通常不一致,所以要采用结构数据类型。
sizeof(x)—— 计算变量x的长度(字节数);
malloc(m) — 开辟m字节长度的地址空间,并返回这段空间的首地址;
free(p) —— 释放指针p所指变量的存储空间,即彻底删除一个变量。
链表的运算效率分析:
(1)查找
因线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为 O(n)。
(2) 插入和删除
因线性链表不需要移动元素,只要修改指针,一般情况下时间复杂度为 O(1)。
但是,如果要在单链表中进行前插或删除操作,因为要从头查找前驱结点,所耗时间复杂度将是 O(n)。
例:在n个结点的单链表中要删除已知结点*P,需找到它的前驱结点的地址,其时间复杂度为 O(n)
顺序存储和链式存储的区别和优缺点?
顺序存储时,逻辑上相邻的数据元素,其物理存放地址也相邻。顺序存储的优点是存储密度大,存储空间利用率高;缺点是插入或删除元素时不方便。
链式存储时,相邻数据元素可随意存放,但所占存储空间分两部分,一部分存放结点值,另一部分存放表示结点间关系的指针。链式存储的优点是插入或删除元素时很方便,使用灵活。缺点是存储密度小,存储空间利用率低。
顺序表适宜于做查找这样的静态操作;
链表宜于做插入、删除这样的动态操作。
若线性表的长度变化不大,且其主要操作是查找,则采用顺序表;
若线性表的长度变化较大,且其主要操作是插入、删除操作,则采用链表。
① 数组中各元素具有统一的类型;
② 数组元素的下标一般具有固定的上界和下界,即数组一旦被定义,它的维数和维界就不再改变。
③数组的基本操作比较简单,除了结构的初始化和销毁之外,只有存取元素和修改元素值的操作。
三元素组表中的每个结点对应于稀疏矩阵的一个非零元素,它包含有三个数据项,分别表示该元素的 行下标 、列下标 和 元素值 。
限定仅在表尾进行插入或删除操作的线性表。
栈的基本操作:在栈顶进行插入或删除,栈的初始化、判空及取栈顶元素等。
入栈口诀:堆栈指针top “先压后加”
出栈口诀:堆栈指针top “先减后弹”
top=0表示空栈。
1)构造一个空栈S
Status InitStack(SqStack &S)
{
S.base = (SElemType *) malloc(STACK_INIT_SIZE * sizeof(SElemType));
if(!S.base) exit (OVERFLOW); //存储分配失败
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
2)返回栈顶元素
Status GetTop(SqStack S, SElemType e)
{//若栈不空,则用e返回S的栈顶元素,并返回OK,否则返回ERROR
if(S.top == S.base) return ERROR;
e = *(S.top-1);
return OK;
}//GetTop
3)顺序栈入栈函数PUSH()
Status Push(SqStack &S, SElemType e)
{ //插入元素e为新的栈顶元素
if(S.top-S.base>=S.stacksize)//栈满,追加存储空间
{
s.base = (SElemType*)realloc(S.base,(S.stacksize+STACKINCREMENT)*sizeof(SElemType));
if(!S.base) exit(OVERFLOW);//存储分配失败
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top++ =e;
return OK:
}//PUSH
4)顺序栈出栈函数POP()
status Pop( SqStack &S,SElemType &e)
{ //若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK,否则返回ERROR
if(S.top == S.base) return ERROR;
e=* —S.top;
return OK;
}
数制转换,括号匹配的检验,行编辑程序,迷宫求解,表达式求值,递归实现。
是一种先进先出的线性表,它只允许在表的一端进行插入,而在另一端删除元素。
允许插入的一端叫做队尾,允许删除的一端叫做队头。
除了栈和队列外,还有一种限定性数据结构是双端队列。双端队列是限定插入和删除操作在表的两端进行的线性表。
typedef Struct QNode{
QElemType data; //元素
Struct QNode *next; //指向下一结点的指针
}Qnode , * QueuePtr ;
链队列类型定义:
typedef struct {
QueuePtr front ; //队首指针
QueuePtr rear ; //队尾指针
} LinkQueue;
① 空链队的特征:front=rear
② 链队会满吗?一般不会,因为删除时有free动作。除非内存不足!
③ 入队(尾部插入):rear->next=S; rear=S;
出队(头部删除):front->next=p->next;
顺序队类型定义:
#define QUEUE-MAXSIZE 100 //最大队列长度
typedef struct {
QElemType *base; //队列的基址
int front; //队首指针
int rear; //队尾指针
}SqQueue
建队核心语句:
q.base=(QElemType )malloc(sizeof (QElemType) QUEUE_MAXSIZE); //分配空间
队空条件 : front = rear (初始化时:front = rear )
队满条件: front = (rear+1) % N (N=maxsize)
队列长度(即数据元素个数):L=(N+rear-front)% N
1)初始化一个空队列
Status InitQueue ( SqQueue &q ) //初始化空循环队列 q
{
q.base=(QElemType )malloc(sizeof(QElemType) QUEUE_MAXSIZE); //分配空间
if (!q.base) exit(OVERFLOW);//内存分配失败,退出程序
q.front =q.rear=0; //置空队列
return OK;
} //InitQueue;
2)入队操作
Status EnQueue(SqQueue &q, QElemType e)
{//向循环队列 q 的队尾加入一个元素 e
if ( (q.rear+1) % QUEUE_MAXSIZE == q.front)
return ERROR ; //队满则上溢,无法再入队
q.rear = ( q.rear + 1 ) % QUEUE_MAXSIZE;
q.base [q.rear] = e; //新元素e入队
return OK;
}// EnQueue;
3)出队操作
Status DeQueue ( SqQueue &q, QElemType &e)
{//若队列不空,删除循环队列q的队头元素,
//由 e 返回其值,并返回OK
if ( q.front = = q.rear ) return ERROR;//队列空
q.front=(q.front+1) % QUEUE_MAXSIZE;
e = q.base [ q.front ] ;
return OK;
}// DeQueue
链队列空的条件是首尾指针相等,而循环队列满的条件的判定,则有队尾加1等于队头和设标记两种方法。
为什么要设计堆栈?它有什么独特用途?
什么叫“假溢出” ?如何解决?
① 调用函数或子程序非它莫属;
② 递归运算的有力工具;
③ 用于保护现场和恢复现场;
④ 简化了程序设计的问题。
2.为什么要设计队列?它有什么独特用途?
① 离散事件的模拟(模拟事件发生的先后顺序,例如 CPU芯片中的指令译码队列);
② 操作系统中的作业调度(一个CPU执行多个作业);
③ 简化程序设计。
答:在顺序队中,当尾指针已经到了数组的上界,不能再有入队操作,但其实数组中还有空位置,这就叫“假溢出”。解决假溢出的途径———采用循环队列。
4.在一个循环队列中,若约定队首指针指向队首元素的前一个位置。那么,从循环队列中删除一个元素时,其操作是先 移动队首位置 ,后 取出元素。
5.线性表、栈、队的异同点:
相同点:逻辑结构相同,都是线性的;都可以用顺序存储或链表存储;栈和队列是两种特殊的线性表,即受限的线性表(只是对插入、删除运算加以限制)。
不同点:① 运算规则不同:
线性表为随机存取;
而栈是只允许在一端进行插入和删除运算,因而是后进先出表LIFO;
队列是只允许在一端进行插入、另一端进行删除运算,因而是先进先出表FIFO。
② 用途不同,线性表比较通用;堆栈用于函数调用、递归和简化设计等;队列用于离散事件模拟、OS作业调度和简化设计等。