数据结构

数据结构绪论:

数据结构:带“结构”的数据元素的集合;

数据元素:是数据集合中的一个“个体”,是数据结构中讨论的基本单位;

数据项:是数据结构中讨论的不可再分的最小单位;

二元组:

格式:

Data_Structure=(D,R)

​ D={di| 1≤i≤n,n≥0}

​ R={rj | 1≤j≤m,m≥0}

​ 注:rj表示集合R中的第j个关系,每个关系用序偶表示。 序偶<x,y>(x,y∈D) x为第1个元素,y为第2个元素。

举例:

LinearList={D,R}

D={A,B,C}

R=

顺序、链式存储结构:逻辑上相邻存储在物理位置(地址)上是否相邻

⭐抽象数据类型:用户定义的、表示应用问题的数学模型以及定义在 这个模型上一组操作的总称。 包含:数据对象、关系集合、基本操作

ADT Complex {

数据对象:

​ D={e1,e2 | e1,e2均为实数}

数据关系:

​ R={<e1,e2> | e1是复数的实数部分,e2 是复数的虚数部分 }

基本操作:

	AssignComplex(&z,v1,v2):构造复数Z 

​	DestroyComplex(&z):销毁复数z 

​	GetReal(z,&real):返回复数z的实部值 

​	GetImag(z,&Imag):返回复数z的虚部值 

​	Add(z1,z2,&sum):返回两个复数z1、z2的和
​	……

} ADT Complex

算法:

​ 算法的定义:对特定问题求解步骤的一种描述,它是指令 的有限序列,每一条 指令表示一个或多个操作。

​ 算法的特性:有穷性、确定性、可行性、输入、输出;

​ 算法的描述:自然语言、流程图、程序设计语言、伪代码;

​ 算法设计的目标:正确性、可使用性、可读性、健壮性、时间效率高与存储量 低;

​ 算法效率:

(1)时间复杂度:可由其基本运算的执行次数来计量;

算法中基本运算执行次数T(n)是问题规模n的某个函数

记作: T(n) = O(f(n))

T(n):基本运算执行次数

n:问题规模

“O”标记的形式定义: 若f(n)是正整数n的一个函数,则T(n)=O(f(n)) 表示存在一个正的常数M,使得当n≥n0时都满足: |T(n)|≤M|f(n)|
换句话说:当自变量n趋向于无穷大时,两者的比值 是一个小于等于M的常数。 如T(n) = n2, f(n) = 3n2+2n+1000

只求出T(n)的最高阶,忽略其低阶项和常系数, 这样既可简化T(n)的计算,又能比较客观地反映 出当n很大时算法的时间性能。

例如,T(n) = 3n2 + 2n + 1000 = O(n2)

​ (2)空间复杂度:算法在运行过程中临时占用的存储空间的度量 。一般也是问题规模n的的函数S(n)=O(g(n))

线性表:

普通线性表:

​ (1)定义:具有相同特性的数据元素的一个有限序列。 一般表示为 (a1, a2 ...,ai-1, ai, ai+1, ..., an) ;

​ (2)基本运算:

▲LocateElem(L,e):返回L中第一个值域与e相等的 逻辑位序。若这样的元素不存在,则返回0
▲GetElem(L,i,&e):用e返回L中第i个元素的值
▲ListInsert(&L,i,e):在L中第i个元素之前插入e ▲ListDelete(&L,i,&e):删除L中第i个元素
▲LocateElem(L,e):返回L中第一个值域与e相等的 逻辑位序。若这样的元素不存在,则返回0
▲GetElem(L,i,&e):用e返回L中第i个元素的值 
▲ListInsert(&L,i,e):在L中第i个元素之前插入e 
▲ListDelete(&L,i,&e):删除L中第i个元素

​ (3)线性表的存储:

​ 顺序存储:

​ 【1】定义:使用一块地址连续的存储空间,按照线性 表中元素的逻辑顺序依次存放相应元素。

​ 【2】⭐随机存储取特性:

​ 利用地址相邻的特性,根据公式进行地址的计算:

​ LOC(ai)=LOC(a1)+(i-1)*len

​ LOC(a1):线性表的基地址

​ len: 每个元素的长度

​ 【3】在C语言中的定义(数组):

#define MaxSize 50 
typedef int ElemType; //ElemType类型实际上是int 
typedef struct
{ 
	ElemType data[MaxSize];  //存放顺序表中的元素 
	int length;              //顺序表的长度 
}SqList;                     //SequenceList, 顺序表

​ 链式存储:

​ 【1】特点:线性表中的数据元素存放在一组地址任意 的存储节点,节点之间使用“链”进行连接。

​ 【2】链表存储密度低:存储密度=数据所占空间/节点所占用空间(节点所占用空间=数据占用空间+指针占用空间)

​ 【3】节点和单链表类型的定义如下(链表):

 typedef struct LNode //定义单链表节点结构体 
 { 
	 ElemType data; //数据域 
	 struct LNode *next; //指向后继节点 
 } LinkList;

​ 【4】重点代码:

①新建节点:

s = new LNode; 
s->data = e; 
s->next = p->next; 
p->next = s;

②删除第i个节点:

q = p->next; 
e = q->data; 
p->next = q->next; 
delete q;

③头插法

void CreateListF(LinkList *&L, ElemType a[], int n) 
{	LinkList *s; 
	int i; 
	L = new LNode; 
	L->next = NULL; //创建头节点,其next域置为NULL 
	for (i=0; i<n; i++){ //循环建立数据节点 
		s = new LNode; 
		s->data = a[i]; //创建数据节点*s 
		s->next = L->next;  //将*s插在原开始节点之前,头节点之后 
		L->next = s; 
	} 
} 

④尾插法

void CreateListR(LinkList *&L, ElemType a[], int n) 
{ 	LinkList *s,*r; 
	int i; L = new LNode;         //创建头节点 
	r=L; //r始终指向尾节点,开始时指向头节点 
	for (i=0; i<n; i++)   //循环建立数据节点 
	{ 	s = new LNode; 
		s->data = a[i];   //创建数据节点*s 
		r->next = s; //将*s插入*r之后 
		r = s; 
	} 
	r->next = NULL; //尾节点next域置为NULL 
}

​ 【5】双链表:节点中有两个指针域 一个指向后继节点,一个指向前驱节点。(方便查找一个节点的前驱与后继)

typedef struct DNode //声明双链表节点类型 
{ 
	ElemType data; 
	struct DNode *prior; //指向前驱节点 
	struct DNode *next; //指向后继节点 
}DLinkList;

栈:

后进先出的线性表.

​ 【1】特点:

​ ①栈顶元素先出栈,栈底元素后出栈;

​ ②时进时出→元素未完全进栈时,即可出栈;

​ 【2】基本操作:

▲InitStack(&S) //操作结果:构造一个空栈 S 
▲DestroyStack(&S) //初始条件:栈 S 已存在 操作结果:栈 S 被销毁 
▲StackEmpty(S) //初始条件:栈S已存在 操作结果:若栈S为空栈,则返回TRUE,否则 FALSE 
▲StackLength(S) //初始条件:栈S已存在。 操作结果:返回S的元素个数,即栈的长度
▲GetTop(S, &e) //查看栈顶元素,不同于Pop 初始条件:栈 S 已存在且非空。 操作结果:用 e 返回 S 的栈顶元素。
▲ClearStack(&S) //初始条件:栈 S 已存在。 操作结果:将 S 清为空栈。 
▲Push(&S, e) //初始条件:栈 S 已存在。 操作结果:插入元素 e 为新的栈顶元素。
▲Pop(&S, &e) //初始条件:栈 S 已存在且非空。 操作结果:删除S的栈顶元素,并用e返回其值

【3】栈的表示与实现:

(1)顺序栈(即栈的顺序存储结构):

​ 一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。

​ ◼ 初始分配量:MAXSIZE

​ ◼ 栈底指针base,总是指向栈底。

​ ◼ 栈顶指针top。栈空时,top等于base;栈非空时, 总是指向栈顶元素+1的位置。

​  插入一个栈顶元素,指针top增1;

		 删除一个栈顶元素,指针top减1; 

​  非空栈中的栈顶指针始终在栈顶元素的下一个位置上。

⭐ 栈的顺序存储表示
#define  MAXSIZE  100;//存储空间初始分配量 
typedef int ElemType; 
typedef int Position; 
typedef struct { 
	ElemType *base; //栈构造前和销毁后,base的值为NULL 
	Position top;   //栈顶指针 
	int  size; //当前已分配的存储空间,以元素为单位
} SqStack;

(2)链栈:

注:链栈中指针的方向指向前驱结点。(头插法)

⭐链栈的定义:
 typedef struct node{
     LElemType data;
     struct node *link; 
 }StackNode,*LinkStack;
链栈的特点:

◼ 插入和删除(进栈/出栈)只能在表头位置(栈顶)进行;

◼ 链栈中的结点是动态产生的,可不考虑上溢问题;

◼ 无需附加头结点,栈顶指针就是链表(即链栈)头指针。

⭐链栈进栈运算:
Status Push(LinkStack &S , ElemType e) 
{/*将元素e进链栈 */ 
	StackNode *p; 
	p = new StackNode;    
	if (!p) exit(OVERFLOW); 
	p->data = e; 
	p->link = S; 
	S = p; 
	return OK; 
} 
⭐链栈出栈运算:
Status Pop(LinkStack &S,SElemType &e) 
{/*从链栈顶取出元素存至e */ 
	if (s->next==NULL) 
		return false; 
	StackNode *p;//临时指针,用来销毁栈顶元素 
	e = S->data; 
	p = S; 
	S = S->next; 
	delete p; 
	return OK; 
}

⭐栈在算法中的作用:暂存程序运行的中间状态

队列:

先进先出(FIFO) 的 线性表. 在表一端插入,在另一 端删除.

​ 【1】基本运算:

▲ InitQueue(&Q) //操作结果:构造一个空队列Q。 
▲ DestroyQueue(&Q) //初始条件:队列Q已存在。 操作结果:队列Q被销毁,不再存在。
▲ QueueEmpty(Q) //初始条件:队列Q已存在。 操作结果:若Q为空队列,则返回TRUE,否则返回 FALSE 
▲ QueueLength(Q) //初始条件:队列Q已存在。 操作结果:返回Q的元素个数,即队列的长度。 
▲ GetHead(Q, &e) //初始条件:Q为非空队列。 操作结果:用e返回Q的队头元素。
▲ ClearQueue(&Q) //初始条件:队列Q已存在。 操作结果:将Q清为空队列。 
▲ EnQueue(&Q, e) //入队 初始条件:队列Q已存在。 操作结果:插入元素e为Q的新的队尾元素。
▲ DeQueue(&Q, &e) //出队 初始条件:Q为非空队列。 操作结果:删除Q的队头元素,并用e返回其值。 

​ 【2】队列类型的实现:

(1)顺序队列:

#define MAXQSIZE  100 //最大队列长度 
typedef struct 
{ 
	QElemType *base; //初始化动态分配存储空间 
	int front, rear ;     /*队首、队尾*/
 }SqQueue; 
 SqQueue Q;
(2)链队列结构:
typedef int QElemType; 
typedef struct QNode {// 结点类型 
	QElemType data; 
	struct QNode *next; 
	} QNode, *QueuePtr; 
typedef struct { // 链队列类型 
	QueuePtr front;  // 队头指针 
	QueuePtr rear;   // 队尾指针
} LinkQueue;

⭐【3】解决假溢出——循环队列

入/出队列运算 利用“模运算”,则: 
入队:Q.rear=(Q.rear+1)%m 
出队:Q.front=(Q.front+1)%m
判断队空队满:
队空:front==rear 队满:front==rear
少用一个元素空间: 
队空:Q.rear = Q.front 
队满:(Q.rear+1) % m = Q.front

串:

【1】定义:由零个或多个字符组成的有限序列,一般记 为:s='a1a2...an' (n≥0)

*串中字符的个数n称为串的长度;包含零个字符, 即长度为零的串称为空串,用ф表示。(非空格串;空格串是一个或多个空格组成的)

​ ◼ 子串:串中任意个连续的字符组成的子序列。

​ ◼ 主串:包含子串的串称为主串。

​ ◼ 位置:字符在序列中的序号。

​ 子串在主串中的位置以子串的第一个字符在主串中 的位置来表示。

​ ◼ 相等:两个串的长度相等,并且对应位置的字符 都相同。

【2】基本运算:

▲ StrEmpty(S) //初始条件:串S已存在。 操作结果:若S为空串,则返回TRUE,否则返回 FALSE。 ''表示空串,空串的长度为零。 
▲ StrCompare(S, T) //初始条件:串S和T存在。 操作结果:若S>T,则返回值>0; 若S=T,则返回值=0; 若S<T,则返回值<0。 等价于C语言中的strcmp函数(按ASCII码值进行大 小比较) 原理:对字符串的每个字符逐个进行比较。
▲ StrLength(S) //初始条件:串 S 存在。 操作结果:返回 S 的元素个数,称为串的长度。 等价于C语言中的strlen函数 
▲ Concat(&T, S1, S2) //初始条件:串 S1 和 S2 存在。 操作结果:用T返回由S1和S2联接而成的新串。
▲ SubString(&Sub, S, pos, len) //初始条件:串 S 存在,1≤pos≤StrLength(S)           且 0≤len≤StrLength(S)-pos+1。 操作结果:用 Sub 返回串 S 的第 pos 个字符 起长度为 len 的子串。 
▲ Index(S, T, pos) 初始条件:串S和T存在,T是非空串, 1≤pos≤StrLength(S)。 操作结果: 若主串 S 中存在和串 T 值相同的子 串, 则返回它在主串 S 中第pos个字符之后第一 次出现的位置;否则函数值为0。 ◼ “子串在主串中的位置”指子串中的第一个字符在 主串中的位序。 
▲ Replace(&S, T, V) 初始条件:串S, T和V均已存在,且T是非空串 操作结果:用 V 替换主串 S 中出现的所有与 (模式串)T 相等的不重叠的子串。
▲ StrInsert (&S, pos, T) 初始条件:串S和T存在, 1≤pos≤StrLength(S)+1 操作结果:在串S的第pos个字符之前插入串T。
▲ StrDelete (&S, pos, len) 初始条件:串S存在1≤pos≤StrLength(S)-len+1。 操作结果:从串S中删除第pos个字符起长度为len 的子串。 
▲ ClearString (&S) 初始条件:串S存在。 操作结果:将S清为空串。

【3】算法

​ BF算法:

​ BF算法的时间复杂度:

若n为主串长度,m为子串长度,最坏情况是

主串前面n-m个位置都部分匹配到子串的最后一位, 即这n-m位各比较了m次

最后m位也各比较了1次 总次数为:(n-m)m+m=(n-m+1)m 若m<<n,则算法复杂度O(n*m)

​ KMP算法:

参考:https://blog.csdn.net/dark_cy/article/details/88698736

代码:

int Index_KMP(SString S, SString T, int pos) { 
    //  1≤pos≤StrLength(S) 
    i = pos;   j = 1; 
    while (i <= S[0] && j <= T[0]) { 
        if (j == 0 || S[i] == T[j]) { ++i;  ++j; } // 继续比较后继字符 
        else j = next[j]; // 模式串向右移动 
    } 
    if (j > T[0])  
        return  i-T[0];  // 匹配成功 
    else return 0;
} // Index_KMP

难题解答:

应用一、括号匹配、中缀表达式转后缀表达式等“匹配”类问题:

解决方法:选择特定部分类型数据入栈、入队,结合实时输出以及暂时储存、调用第二个栈等。

例如(中缀转后缀):

参考来源:https://blog.csdn.net/weixin_44260779/article/details/90695746

应用二、迷宫问题(回溯法、广度遍历优先、深度遍历优先):

解决方法:利用栈来存储临时存储大量的临时数据,结合题目设置的判定条件使用各类判断如if,循环如while语句判断当前数据是否“合法”,合法入栈非法退栈,再利用递归等方法实现。

⭐复杂递归函数:多次分解求值

⭐广度遍历优先(最优解)代码:

private WideBlock wb;                             //广度遍历的终点节点

    /*
        广度优先遍历
    */
    public void findPath4(){
        wideFirst();
    }
    
    public void wideFirst(){
        WideBlock start = new WideBlock(startX , startY , null);
        Queue<WideBlock> q = new LinkedList<WideBlock>();  
        map[startX][startY] = 1;                       
        q.offer(start);
        while(q.peek() != null){
            WideBlock b = q.poll();
            int x = b.getX();
            int y = b.getY();
            if(x == endX && y == endY){                 //判断当前节点是否已达终点
                wb = b;
                return;
            }
            if (x - 1 >= 0 && map[x - 1][y] == 0) {     //判断当前节点的能否向上走一步,如果能,则将下一步节点加入队列
                q.offer(new WideBlock(x - 1 , y , b));  //子节点入队
                map[x - 1][y] = 1;                      //标记已访问
            }
            if (y + 1 <= mapY && map[x][y + 1] == 0) {  //判断当前节点能否向右走一步
                q.offer(new WideBlock(x , y + 1 , b));
                map[x][y + 1] = 1;
            }
            if (x + 1 <= mapX && map[x + 1][y] == 0) {
                q.offer(new WideBlock(x + 1 , y , b));
                map[x + 1][y] = 1;
            }
            if (y - 1 >= 0 && map[x][y - 1] == 0) {
                q.offer(new WideBlock(x , y - 1 , b));
                map[x][y -1] = 1;
            }
        }
    }


    //打印广度遍历的路径
    public void printWidePath(){
        WideBlock b = wb;
        int count = 1;
        while(b != null){
            System.out.print("(" + b.getX() + "," + b.getY() + ") ");
            b = b.getParent();
            if(count++ % 10 == 0){
                System.out.println("");
            }
        }
    }

参考来源:https://www.cnblogs.com/wanghang-learning/p/9430672.html

posted @ 2020-03-28 19:29  蛋炒饭特稀  阅读(366)  评论(0编辑  收藏  举报