Data Structure 概述 基本概念
算法
算法:算法是对特定问题求解步骤的一种描述,他是指令的有限序列,其中每一条指令表示一个或多个操作。
算法的特征:
有穷性:一个算法必须总是在执行有穷步骤之后结束,且每一步都可在有穷时间内完成
确定性:算法中每一条指令必须有确切的含义,读者理解时不会产生二义性,并且,在任何条件下,算法只有唯一的一条执行路径,即对于相同的输入只能得出相同的输出
可行性:一个算法是能行的,即算法中描述的操作都是可以通过已经实现的基本运算执行有限次来实现的
输入:一个算法有零个或多个输入,这些输入取自于某个特定的对象的集合
输出:一个算法有一个或多个输出,这些输出是同输入有这某些特定的关系
算法设计的要求:
正确性(一般情况下,要求达到 C 层)
可读性
健壮性
效率与低存储量需求
算法的时间复杂度:一般情况下,算法中基本操作重复执行的次数是问题规模 n 的某个函数 f(n),算法的时间度量记作:,表示随问题规模 n 的增大,算法执行时间的增长率和 f(n) 的增长率相同
语句频度用归纳法进行计算
算法的空间复杂度:记作 作为算法所需存储空间的量度
其中 n 为问题的规模(或大小)。
时间复杂度例题:
例1:
1 2 3 int sum = 0 ,n = 100 ; sum = (1 + n) * n/2 ; printf ("%d" , sum);
时间复杂度为:根据推导大O阶方法第1条,用常数1取代运行时间中的所有加法常数。
所以时间复杂度为 O(1)
例2:
1 2 int i; for (i=0 ;i<n;i++);
根据在修改后的运行次数函数中,只保留最高阶项法则。
所以时间复杂度为 O(n)
例3:
1 2 3 int i,j; for (i=0 ;i<n;i++) for (j=0 ;j<n;j++);
所以时间复杂度为 O($n^2$)
例4:
1 2 3 int i,j; for (i=0 ;i<n;i++) for (j=i;j<n;j++);
int执行一次,下面的for执行了n+(n-1)+(n-2)+…+1次,所以T(n)=1+(1+n)*n/2=n2/2+n/2+1。
所以时间复杂度为 O($n^2$)
例5:
1 2 3 int i=1 ;while (i<n) i*=2 ;
计算时间复杂度, int i = 1;执行一次,在 while 循环中,设需要执行的次数为 x。则:
i=2,每执行一次,i 的值都将会乘以2,即为1\ 2*2*2*2*2……所以第 x 次 i 的值为$2^x$,所以$2^x$>=n时,程序截止。所以 x=logn
所以时间复杂度为 O(logn)
线性表 线性表及其特点:
线性表是 n 个数据元素的有限序列
线性结构的特点:(在非空有限集中)
存在唯一的被称为“第一个”的数据元素
存在唯一的被称为“最后一个”的数据元素
除第一个外,集合中的每个元素均只有一个“前驱”
除第一个外,集合中的每个元素均只有一个“后继”
顺序表——线性表的顺序存储结构
特点:
线性表的抽象数据类型(ADT 描述):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ADT List{ 数据对象:D={ai|ai属于 ElemSet,i=1 ,2 ,…,n,n≥0 } 数据关系:R1={<ai-1 ,ai> | ai-1 ,ai 属于 D,i=2 ,…,n} 基本操作: Initlist(&L) 操作结果:构造一个空的线性表 L DestroyList(&L) 初始条件:线性表 L 已经存在 操作结果:销毁线性表 L ClearList(&L) 初始条件:线性表 L 已经存在 操作结果:将 L 重置为空表 ListEmpty(L) 初始条件:线性表 L 已经存在 操作结果:判断 L 是否为空,若空返回 TRUE,否则返回 FALSE ListLength(L) 初始条件:线性表已经存在 操作结果:返回 L 中元素的个数 GetList(L,i,&e) 初始条件:线性表 L 已经存在,1 ≤i≤ListLength(L) 操作结果:用 e 返回 L 中第 i 个数据元素的值 LocateElem(L,e,compare()) 初始条件:线性表 L 已经存在,compare()是数据元素判定函数 操作结果:返回 L 中第1 个与 e 满足关系 compare()的元素的位 序。若这个元素不存在,则返回值为0. ListInsert(&L,i,e) 初始条件:线性表 L 已经存在,1 ≤i≤Listlength(L)+1 操作结果:在 L 中第 i 个位置之前插入新的数据元素 e,L 的长度 加1 ListDelete(&L,i,&e) 初始条件:线性表已经存在且非空,1 ≤i≤ListLength(L) 操作结果:删除 L 的第 i 个数据元素,并用 e 返回其值,L 的长度 减1 }
首先,定义顺序表存储结构体: 1 2 3 4 5 6 typedef int datatype; typedef struct { datatype a[maxsize]; int len; }list ;
线性表初始化算法 1 2 3 4 void initlist (list *l) { l->len = 0 ; }
线性表 addlist 添加算法 条件:表未满
1 2 3 4 5 6 7 8 9 void addlist (list *, datatype x) { if (l->len == MAXSIZE) { printf ("The list is full!" ) } l->a[l->len] = x; l->len = l->len + 1 ; }
线性表的插入算法 条件:
表未满
合理的插入范围 1≤i≤L.length+1(即从表的第一个到最后之后的一个是可以插入的范围,因为最后一个之后还可以继续相连,也就是挨着这也符合顺序表的定义特点)
步骤:
第 i 至最后所有的元素后移一个元素
在第 i 个位置插入元素 x
表长增加1
算法 (注意到:从 i 到最后一个元素后移一个元素的时候联想排座位后移,需要先从最后一个开始移动)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bool insertlist (list *l, int i, datatype x) { if (l->len == MAXSIZE) { printf ("The list is full!" ) } if (i >= 1 || i <= l->len + 1 ) { for (int j = l->len-1 ;j >= i-1 ;j--) { l->a[j+1 ] = l->a[j]; } l->a[i-1 ] = x; l->len++; } return true ; }
线性表的删除算法 条件:
表非空
合理的删除范围 (1≤i≤L.length)
步骤:
取出第 i 个元素
将 第 i 个元素之后的所有元素向前移动一个位置
算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void deletelist (list *l, int i, datatype e) { if (l->len == 0 ) { printf ("The list is none!" ); } if (i < 0 || i > l->len) { printf ("Position is wrong!" ); } for (int j = i; j <= l->len - 1 ; j++) { l->a[j-1 ] = l->a[j]; } l->len--; return true ; }
线性表的打印算法 条件:
线性表非空
算法:
1 2 3 4 5 6 7 8 9 10 11 12 void printlist (list l) { if (l.len == 0 ); { printf ("The list is none!" ); return ; } for (int i = 0 ; i < l.len; i++) { printf ("%5d" , l.a[i]); } }
判断线性表是否为空 算法:
1 2 3 4 int empty (list l,) { return (l.len == 0 ? 1 : 0 ); }
单链表:线性表的链式存储结构之一 malloc()和 free()分别实现内存空间的动态分配和回收,所有一般用户不必知道某个结点具体地址值是多少。
概念: 线性链表、单链表、结点;数据域,指针域;头指针,头结点;
特点: 用指针表示数据之间的逻辑关系(逻辑相邻的元素物理位置不一定相邻)
类型定义: “数据+指针”
空的单链表和非空的单链表
结点:包括数据域和指针域
指针域中存储的信息称作指针或者链。
n 个结点(1≤i≤n)的存储镜像链结成一个链表,即为线性表(a1,a2,…,an)的链式存储结构。由于此链表的每个结点中只包含一个指针域,故又称 线性链表 或 单链表 。
整个链表的存取必须从头指针 开始进行,头指针指示链表中第一个借点(即第一个数据元素的存储映像)的存储位置。同时,由于最后数据元素没有直接后继,则线性链表中最后一个结点的指针为“空”(NULL)
单链表的类型描述
1 2 3 4 5 6 7 8 9 10 11 12 13 ADT link_list{ 数据集合 K:K = {k1,k2,…,kn},n≥0 ,k 中的元素是 datatype 类型; 数据关系 R:R = {r} r = {<Ki,Ki+1 > | i=1 ,2 ,…,n-1 } 操作集合如下: (1 )node *init()建立一个空的单链表 (2 )void display (node *head) 输出单链表中各个结点的值 (3 )node *find (node *head, int i) 在单链表中查找第 i 个结点 (4 )node *insert (node *head, datatype x, int i) 在单链表中第i 个结点后插入一个值为x 的新结点 (5 )node *delete (node *head, datatype x)在单链表中删除一个值为 x 的结点 }ADT link_list
单链表的 C 语言描述
1 2 3 4 5 typedef int datatype;typedef struct LNode { datatype data; struct LNode *next ; }node,
初始化 1 2 3 4 node *init () { return NULL ; }
初始化带头结点的单链表 1 2 3 4 5 6 7 node *init () { node *head; head = (node *)malloc (sizeof (node)); head->next = NULL ; return head; }
打印各个元素的值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void printlinklist (node *head) { node *p; p = head; if (!p) printf ("\n The linklist is none!" ); else { printf ("\n The value of every node in linklist is: \n" ); while (p) { printf ("%5d" , p->data); p = p->next; } } }
在链表中查找第 i 个元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 node *find (node *head, int i) { int j = 1 ; node *p = head; if (i < 1 ) { printf ("The position is wrong!" ); return NULL ; } while (p && i != j) { p = p->next; j++; } return p; }
在链表中查找值为 x 的元素 1 2 3 4 5 6 7 8 9 node *findxlist (node *head, datatype x) { node *p = head; while (p && p->data != x) { p = p->next; } return p; }
插入算法 listinsert(带头结点的链表)
技巧:画图辅助分析
思路:现查找第 i-1 个元素,若找到,在其后插入新元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static listinsert (node *l, int i, datatype x) { node *p = l, *s = NULL ; int j = 0 ; while (p&&j<i-1 ) { p=p->next;j++; } if (p&&j = i-1 ) { s = (node *)malloc (sizeof (node)); s->data = x; s->next = p->next; p->next = s; return true ; } else return false ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 node *insertllist1 (node*head,datatype x,int i) { node *p = head; node *s = NULL ; int j = 0 ; while (p&&j<i-1 ) { p=p->next;j++; } if (p&&j==i-1 ) { *s = (node *)malloc (sizeof (node)); s->info = x; s->next = p->next; p->next = s; return head; } }
删除算法 ListDelete(带头结点的链表) 思路
先查找第 i-1个元素,若找到且其后存在第 i 个元素,则用 x 返回数据,并删除之
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static listdelete (node *l, inti, datatype *x) { node *p = l; node *s = NULL ; int j = 0 ; while (p&&j < i-1 ) { p = p->next; j++; } if (p && j == i-1 && p->next ) { s = p->next; p->next = s->next; x = s->data; free (s); return true ; } else return false ; }
注:
要求先找到第 i-1个元素
能够进行删除的条件:p&&j=i-1&&p->next。条件中的 p->next 保证了第 i 个元素的存在,否则也是无法删除的。
将两个有序链表并为一个有序链表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void mergelist (node *la, node *lb, node *lc) { node *pa = la->next; node *pb = lb->next; node *pc = la->next; while (pa && pb) { if (pa->data <= pb->data) { pc->next = pa; pc = pa; pa=pa->next; } else { pc->next = pb; pc = pb; pb = pb->next; } } pc->next = pa ? pb:pa free (lb); }
建立链表的方式
顺序建表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void createlinklist (node *l, int n) { l = (node *)malloc (sizeof (node)); l->next = NULL ; node *p; p = l; for (i = 0 ; i < n; i++) { scanf (x); node * s = (node *)malloc (sizeof (node)); s->data = x; s->next = p->next; p->next = s; p = s; } }
逆序建表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void createlinklist (node *l, int n) { node *s = NULL ; *l = (node *)malloc (sizeof (node)); l->next = NULL ; for (i = 0 ; i < n; i++) { scanf (x); s = (node *)malloc (sizeof (node)); s->data = x; s->next = l->next; l->next = s; } }
循环链表 循环单链表 初始化一个循环单链表 1 2 3 4 node *init () { return NULL ; }
获得循环单链表最后一个结点的存储地址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 node *rear (node *head) { node *p; if (!head) p = NULL ; else { p = head; while (p->next != head) { p = p->next; } } return p; }
双向链表
类型定义 1 2 3 4 typedef struct dlink_node { datatype date; struct dlink_node *llink ,rlink ; }dnode;
初始化 1 2 3 4 dnode *init () { return NULL ; }
输出各个结点的值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void display (dnode *head) { dnode *p; printf ("\n" ); p = head; if (!p) printf ("\n The list is empty!" ); else { printf ("\n The list is:" ); while (p) { printf ("%5d" ,p->data); p=p->llink; } } }
查找第 i 个结点的存储地址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 dnode *find (dnode *head; int i) { int j = 1 ; dnode *p = head; if (i<1 ) { printf ("The position is wrong!\n" ); return NULL ; } while (p&&j!=i) { p = p->llink; j++; } if (!p) { printf ("The positon don't exist!\n" ); } return p; }
插入操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 dnode *insert (dnode *head, int i,datatype x) { dnode *p,*q; p = (dnode *)malloc (sizeof (dnode)); p->data = x; if (i==0 ) { p->llink = NULL ; p->rlink = head; if (!head) { head = p; } else { head->llink = p; head = p; } return head; } q = find (head, i); if (!q) { printf ("The position don't exist!\n" ); return head; } if (q->rlink == NULL ) { p->rlink = q->rlink; p->llink = q; q->rlink = p; } else { p->rlink = q->rlink; p->llink = q; q->rlink->llink = p; q->rlink = p; } return head; }
删除操作(删除一个值为 x 的结点) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 dnode *delete (dnode *head, datatype x) { dnode *q; if (!head) { printf ("The list is none!\n" ); return head; } q = head; while (q&&q->data != x) { q = q->rlink; } if (!q) { printf ("x doesn't exist!\n" ); } if (q==head && head->rlink) { head = head->rlink; head->llink = NULL ; free (q); return head; } if (q==head && !head->rlink) { free (q); return NULL } else { if (!q->rlink) { q->llink->rlink = NULL ; free (q); return head; } else { q->llink->rlink = q->rlink; q->rlink->llink = q->llink; free (q); return head; } } }
带头结点的双向链表的初始化 1 2 3 4 5 6 7 8 dlink *init () { dlink *head; head = (dnode *)malloc (sizeof (dnode)); head->prior = NULL ; head->next = NULL ; return head; }
带头结点双向链表的插入算法 条件: i范围为 1≤i≤ 表长+1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 dlink *insertlist (dlink *head, int i, datatype x) { dlink *p, *q; int j=0 ; p = head; if (i<0 ) { printf ("The position is wrong!\n" ); return head; } while (p&&j<i-1 ) { p = p->next; j++; } if (p&&j=i-1 ) { q = (dlink *)malloc (sizeof (dlink)); q->data = x; q->next = p->next; p->next->prior = q; p->next = q; q->prior = p; } return head; }
带头结点的双向链表的删除算法 条件:i范围为1≤i≤ 表长
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 dlink *delete (dlink *head; int i, datatype x) { dlink *p, *q; p = head; int j = 1 ; if (i<0 ) { printf ("The position is wrong!\n" ); return head; } while (p&&j!=i) { p = p->next; j++; } if (p&&j=i) { p->prior->next = p->next; p->next->prior = p->prior; free (p); return head; } }
栈 基本概念及描述: 栈是一种特殊的线性表,规定它的插入运算和删除运算均在线性表的同一端进行,进行插入的和删除的那一端称为栈顶,另一端称为栈底。
栈的插入和删除操作分别简称为:入栈、出栈
栈的特点:先进后出