沿途的风景要比目的地更弯的否
程序的Bigger灵魂
- 上学时听到老师说过:“程序设计 = 数据结构 + 算法”。当时的自己理解这句话,挺精辟的。 工作几年后,再次看到这句话,“我X,真TMD精辟!!!”
- 前段时间,看过的一本书中有过这段话:I/O是一个程序的灵魂。如果一个没有输入和输出的程序是没有意义的,也就没有了灵魂。
- 因此总结:数据结构 + 算法是对程序灵魂的封装,更高Bigger
1.数据结构
好的程序设计 = 数据结构 + 算法.
1.1相关概念
数据结构基本概念:数据,数据元素,数据项,数据对象,数据结构
1.1.1 数据
数据: “是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。数据不仅仅包括整型、实型等数值类型,还包括字符及声音、图像、视频等非数值类型。” 例如,Mp3就是声音数据,图片就是图像数据. 而这些数据,其实只不过是"符号". 这些符号有2个前提:
- 可以输入到计算机中
- 能被计算机程序处理.
对于从后端返回的整型,浮点型数据,可以直接进行数值计算. 对于字符串类型的数据,我们就需要进行非数值的处理. 而对于声音,图像,视频这样的数据,我们需要通过编码的手段将其变成二进制数据进行处理.
1.1.2. 数据元素
数据元素: 是组成数据的,且有一定意义的基本单位,在计算机中通常作为整体处理. 也被称作"记录"
例如,我们生活的圈子里.什么叫数据元素了? 人Person ,汽车Car 等.
1.1.3. 数据项
数据项: 一个数据元素可以由若干数据项组成. 比如,Person 数据元素,可以为分解为眼睛,耳朵,鼻子,嘴巴,手臂这些基本的数据项,也可以从另外的角度拆解成姓名,年龄,性别,出生地址,出生日期,联系电话等数据项. 那么你如何拆解数据项, 要看你的项目来定. 数据项是数据不可分割的最小单位. 在我们的课程中,我们把数据定位为最小单位.这样有助于我们更好理解以及解决问题.
1.1.4. 数据对象
数据对象: 是性质相同的数据元素的集合,是数据的子集.
那么什么叫性质相同? 是指数据元素具有相同数量和类型的数项. 类似数组中的元素保持性质一致.
1.1.5. 数据结构
结构,简单理解就是关系. 比如分子结构,就是说组成分子原子的排列方式. 不同数据元素之间不是独立的,而是存在特定的关系.我们将这些关系成为结构. 那么数据结构是什么? 数据结构是相互之间存在一种或多种特定关系的数据元素的集合.
之间的关系可以参考下面的图片和资料
1.2逻辑结构和物理结构
1.2.1逻辑结构
-
线性结构
线性结构: 线性结构中的数据元素之间是一对一的关系.常用的线性结构有:线性表,栈和队列,字符串等。 -
非线性结构 非线性结构:结构中的数据原型不是一对一的关系。可能是没有对应关系或者一对多的关系。例如:集合结构、树形结构、图形结构
1.2.2物理结构
物理结构,别称"存储结构". 顾名思义,指的是数据的逻辑结构在计算机的存储形式.
- 顺序存储
- 链式存储
2.算法
算法: 是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作.
2.1算法与数据结构关系
2.2特性
- 输入输出:
输入输出,很好理解. 在解决问题时必须有已知条件,当然有些算法可能没有输入. 但是算法至少有一个或多个输出.否则没有输出,没有结果.你用这个算法干吗?
- 有穷性:
有穷性: 指的是算法在执行有限的步骤之后,自动结束而不会出现无限循环,且每一个步骤在可接受的时间内完成.
- 确定性:
确定性: 算法的每一个步骤都具有确定的含义,不能出现二义性; 算法在一定条件下,只有一条执行路径,相同的输入只能有唯一的输出结果.
- 可行性:
可行性: 算法的每一步都必须是可行的,换句话说,每一步都能通过执行有限次数完成.
2.3评价标准
- 正确性
算法的正确性是指算法至少应该具有输入,输出和加工处理无歧义性,能正确反映问题的需求,能够得到问题的正确答案; 正确分为4个层次: (1).算法程序没有语法错误; (2).算法程序对于合法的输入数据能够产生满足要求的输出结果; (3).算法程序对于非法的输入数据能够得出满足规格说明的结果;(4).算法程序对于精心选择的,甚至刁钻的测试数据都有满足要求的输出结果;
- 可读性
可读性: 算法设计的另一个目的是为了便于阅读,理解和交流; 可读性高有助于人们理解算法,晦涩难懂的算法往往隐含错误,且不容易发现并且难于调试和修改; 注意, 不要犯初学者的错误; 认为代码越少,就越牛逼! 如果有这样的想法, 在团队协作的今天.不再是个人英雄主义的时代! 可读性是算法好坏的很重要的标志!
- 健壮性
一个好的算法还应该能对输入数据的不合法的情况做出合适的处理.考虑边界性,也是在写代码经常要做的一个处理; 比如,输入时间或者距离不应该是负数;
健壮性: 当输入数据不合法时,算法也能做出相关处理,而不是产生异常和莫名其妙的结果;
- 时间效率高和储存量低
生活中,人们希望花最少的钱,用最短的时间,办最大的事. 算法也是一样的思想. 用最少的存储空间,花最少的时间,办成同样的事.就是好算法!
2.4效率度量
通常是用时间复杂度和空间复杂度这两个维度来表示,常用的表示方法是大O表示法
2.4.1大O表示法
在进行算法分析时,语句的总执行次数T(n)是关于问题规模n的函数. 进而分析T(n)随着n变化情况并确定T(n)的数量级. 算法的时间复杂度,也就是算法的时间量度. T(n) = O(f(n)). “它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。” 大写O( )来体现算法时间复杂度的记法,我们称之为大O记法。
3.线性表
属于逻辑结构 1对1
3.1顺序存储
3.1.1自定义类型
#define MAXSIZE 100
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
/* ElemType类型根据实际情况而定,这里假设为int */
typedef int ElemType;
/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Status;
/*线性结构使用顺序表的方式存储*/
//顺序表结构设计
typedef struct {
ElemType *data;
int length;
}Sqlist;
3.1.2初始化
Status InitList(Sqlist *L){
//为顺序表分配一个大小为MAXSIZE 的数组空间
L->data = malloc(sizeof(ElemType) * MAXSIZE);
//存储分配失败退出
if(!L->data) exit(ERROR);
//空表长度为0
L->length = 0;
return OK;
}
3.1.3插入
Status ListInsert(Sqlist *L,int i,ElemType e){
//i值不合法判断
if((i<1) || (i>L->length+1)) return ERROR;
//存储空间已满
if(L->length == MAXSIZE) return ERROR;
//插入数据不在表尾,则先移动出空余位置
if(i <= L->length){
for(int j = L->length-1; j>=i-1;j--){
//插入位置以及之后的位置后移动1位
L->data[j+1] = L->data[j];
}
}
//将新元素e 放入第i个位置上
L->data[i-1] = e;
//长度+1;
++L->length;
return OK;
}
3.1.4取值
Status GetElem(Sqlist L,int i, ElemType *e){
//判断i值是否合理, 若不合理,返回ERROR
if(i<1 || i > L.length) return ERROR;
//data[i-1]单元存储第i个数据元素.
*e = L.data[i-1];
return OK;
}
3.1.5删除
Status ListDelete(Sqlist *L,int i){
//线性表为空
if(L->length == 0) return ERROR;
//i值不合法判断
if((i<1) || (i>L->length+1)) return ERROR;
for(int j = i; j < L->length;j++){
//被删除元素之后的元素向前移动
L->data[j-1] = L->data[j];
}
//表长度-1;
L->length --;
return OK;
}
3.1.6清空
Status ClearList(Sqlist *L)
{
L->length=0;
return OK;
}
3.1.7判断是否清空
Status ListEmpty(Sqlist L)
{
if(L.length==0)
return TRUE;
else
return FALSE;
}
3.1.8获取长度
int ListLength(Sqlist L)
{
return L.length;
}
3.1.9输出
Status TraverseList(Sqlist L)
{
int i;
for(i=0;i<L.length;i++)
printf("%d\n",L.data[i]);
printf("\n");
return OK;
}
3.2链式存储
3.2.1自定义类型
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
//定义结点
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node * LinkList;
3.2.2初始化
Status InitList(LinkList *L){
//产生头结点,并使用L指向此头结点
*L = (LinkList)malloc(sizeof(Node));
//存储空间分配失败
if(*L == NULL) return ERROR;
//将头结点的指针域置空
(*L)->next = NULL;
return OK;
}
3.2.3插入
Status ListInsert(LinkList *L,int i,ElemType e){
int j;
LinkList p,s;
p = *L;
j = 1;
//寻找第i-1个结点
while (p && j<i) {
p = p->next;
++j;
}
//第i个元素不存在
if(!p || j>i) return ERROR;
//生成新结点s
s = (LinkList)malloc(sizeof(Node));
//将e赋值给s的数值域
s->data = e;
//将p的后继结点赋值给s的后继
s->next = p->next;
//将s赋值给p的后继
p->next = s;
return OK;
}
3.2.4取值
Status GetElem(LinkList L,int i,ElemType *e){
//j: 计数.
int j;
//声明结点p;
LinkList p;
//将结点p 指向链表L的第一个结点;
p = L->next;
//j计算=1;
j = 1;
//p不为空,且计算j不等于i,则循环继续
while (p && j<i) {
//p指向下一个结点
p = p->next;
++j;
}
//如果p为空或者j>i,则返回error
if(!p || j > i) return ERROR;
//e = p所指的结点的data
*e = p->data;
return OK;
}
3.2.5删除
Status ListDelete(LinkList *L,int i,ElemType *e){
int j;
LinkList p,q;
p = (*L)->next;
j = 1;
//查找第i-1个结点,p指向该结点
while (p->next && j<(i-1)) {
p = p->next;
++j;
}
//当i>n 或者 i<1 时,删除位置不合理
if (!(p->next) || (j>i-1)) return ERROR;
//q指向要删除的结点
q = p->next;
//将q的后继赋值给p的后继
p->next = q->next;
//将q结点中的数据给e
*e = q->data;
//让系统回收此结点,释放内存;
free(q);
return OK;
}
3.2.6清空
Status ClearList(LinkList *L)
{
LinkList p,q;
p=(*L)->next; /* p指向第一个结点 */
while(p) /* 没到表尾 */
{
q=p->next;
free(p);
p=q;
}
(*L)->next=NULL; /* 头结点指针域为空 */
return OK;
}
3.2.7输出
Status ListTraverse(LinkList L)
{
LinkList p=L->next;
while(p)
{
printf("%d\n",p->data);
p=p->next;
}
printf("\n");
return OK;
}
3.2.8单链表前插入法
void CreateListHead(LinkList *L, int n){
LinkList p;
//建立1个带头结点的单链表
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
//循环前插入随机数据
for(int i = 0; i < n;i++)
{
//生成新结点
p = (LinkList)malloc(sizeof(Node));
//i赋值给新结点的data
p->data = i;
//p->next = 头结点的L->next
p->next = (*L)->next;
//将结点P插入到头结点之后;
(*L)->next = p;
}
}
3.2.9单链表后插入法
void CreateListTail(LinkList *L, int n){
LinkList p,r;
//建立1个带头结点的单链表
*L = (LinkList)malloc(sizeof(Node));
//r指向尾部的结点
r = *L;
for (int i=0; i<n; i++) {
//生成新结点
p = (Node *)malloc(sizeof(Node));
p->data = i;
//将表尾终端结点的指针指向新结点
r->next = p;
//将当前的新结点定义为表尾终端结点
r = p;
}
//将尾指针的next = null
r->next = NULL;
}