一、什么是数据结构?
- 首先我们可以考虑一个问题,如何判断程序设计的维度是什么?其实无外乎两点数据结构与算法;也就说好的程序设计 = 数据结构 + 算法.
- 数据结构的组成
- 数据对象:性质相同的数据元素的集合(线性,非线性),可以对照数组
- 数据元素:组成数据对象的基本单位,可以看着数组中的单个对象
- 数据项:程序的操作对象,用于描述客观事物(对应实际变成中的Object的属性)

#include <stdio.h>
//声明一个结构体类型
struct Person{
char *name; //数据项--名字
char *sex; //数据项--性别
int age; //数据项--年龄
};
// Person 就是一种数据结构
// name 、 sex 、age 则是数据的数据项
int main(int argc, const char * argv[]) {
struct Person p; //数据元素;
struct Person pArray[10]; //数据对象;
p.age = 18; //数据项
p.name = "Dean"; //数据项
p.sex = "男"; //数据项
return 0;
}
二、算法的时间复杂度计算(大O表示法)
简单来说大O表示法时间复杂度计算会取方程式计算中最大的函数的值,1取代程序中的所以常数值, 时间复杂度术语:
- 常数阶
- 顺序执行的代码 如上中的main()中的 struct Person p ;执行到 p.sex = "男"; 时间复杂度为O(1)
- 线性阶
- for (i=0 ,i<n,i++){} 程序执行n次 时间复杂度为O(n)
- 平方阶 冒泡算法就是典型的平方阶 O(n^2) *
- 对数阶 首先引入下对数的表达式(如果a的x次方等于N(a>0,且a不等于1),那么数x叫做以a为底N的对数记作x=logaN) *
- 立方阶 O(n^3) 可以理解为三次for循环嵌套的程序 *
- nlog阶 *
- 指数阶 O(2^n) 或者O(n!) 此种情况一般不考虑,算法时间复杂度呈指数增长是一件非常可怕的事情,一般会导致内存快速增长之后Crash
*
定义
/* 1. 常数阶时间复杂度计算 O(1) */
//1+1+1 = 3 O(1)
void testSum1(int n){
int sum = 0; //执行1次
sum = (1+n)*n/2; //执行1次
printf("testSum1:%d\n",sum);//执行1次
}
//1+1+1+1+1+1+1 = 7 O(1)
void testSum2(int n){
int sum = 0; //执行1次
sum = (1+n)*n/2; //执行1次
sum = (1+n)*n/2; //执行1次
sum = (1+n)*n/2; //执行1次
printf("testSum2:%d\n",sum);//执行1次
}
//x=x+1; 执行1次
void add(int x){
x = x+1;
}
/*2.线性阶时间复杂度*/
//x=x+1; 执行n次 O(n)
void add2(int x,int n){
for (int i = 0; i < n; i++) {
x = x+1;
}
}
//1+(n+1)+n+1 = 3+2n -> O(n)
void testSum3(int n){
int i,sum = 0; //执行1次
for (i = 1; i <= n; i++) { //执行n+1次
sum += i; //执行n次
}
printf("testSum3:%d\n",sum); //执行1次
}
/*3.对数阶*/
/*2的x次方等于n x = log2n ->O(logn)*/
void testA(int n){
int count = 1; //执行1次
//n = 10
while (count < n) {
count = count * 2;
}
}
/*4.平方阶*/
//x=x+1; 执行n*n次 ->O(n^2)
void add3(int x,int n){
for (int i = 0; i< n; i++) {
for (int j = 0; j < n ; j++) {
x=x+1;
}
}
}
//n+(n-1)+(n-2)+...+1 = n(n-1)/2 = n^2/2 + n/2 = O(n^2)
//sn = n(a1+an)/2
void testSum4(int n){
int sum = 0;
for(int i = 0; i < n;i++)
for (int j = i; j < n; j++) {
sum += j;
}
printf("textSum4:%d",sum);
}
//1+(n+1)+n(n+1)+n^2+n^2 = 2+3n^2+2n -> O(n^2)
void testSum5(int n){
int i,j,x=0,sum = 0; //执行1次
for (i = 1; i <= n; i++) { //执行n+1次
for (j = 1; j <= n; j++) { //执行n(n+1)
x++; //执行n*n次
sum = sum + x; //执行n*n次
}
}
printf("testSum5:%d\n",sum);
}
/*5.立方阶*/
void testB(int n){
int sum = 1; //执行1次
for (int i = 0; i < n; i++) { //执行n次
for (int j = 0 ; j < n; j++) { //执行n*n次
for (int k = 0; k < n; k++) {//执行n*n*n次
sum = sum * 2; //执行n*n*n次
}
}
}
}
int main(int argc, const char * argv[]) {
testSum1(100);
testSum2(100);
testSum3(100);
return 0;
}

三、算法的空间复杂度计算(大O表示法)
空间复杂度的计算与时间复杂的计算不同,空间复杂度主要考虑执行时需要其它数据的大小的辅助空间
- 比如a,b交换数据,借用1个中间变量c,此时空间复杂度为常数阶O(1),当然不借用c也可以实现,也是常数阶O(1)
- 通过新建数组保存当前数组数据,之后对当前数据在进行操作,此时新建数组大小与当前数组对大小n相关,此时空间复杂度为O(n)
程序空间计算因素:
- 寄存本身的指令
- 常数
- 变量
- 输入
- 对数据进行操作的辅助空间
逻辑结构与物理结构
根据视角不同,数据结构分为2种:逻辑结构与物理结构
-
逻辑结构:逻辑结构指的是数据对象中数据元素的相互关系,逻辑结构分为四种:集合结构,线形结构,树形结构,图形结构
-
集合结构:数据元素同属于一个集合但是彼此是无序没有关联的

-
线性结构:数据元素之间存在一对一的对应关系,常用线性结构有:数组、线性表、栈、队列,字符串

-
树形结构:树形结构是重要的非线性结构,数据元素之间存在一对多的层次关系,常见树形结构:二叉树,红黑树,哈夫曼树等

-
图形结构:数据元素之间存在多对多的对应关系,常见的图形结构有:邻接矩阵,邻接表

-
-
物理结构:物理结构又称为存储结构,是按照数据的逻辑结构在计算机中的存储形式
-
顺序存储结构:把数据元素存储在连续的地址空间,数据间的逻辑关系与物理关系一致,数组开辟空间就是典型的顺序存储按照索引可以快速的查找到对应数据元素的物理地址

-
链式存储结构:是把数据元素放到任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的,数据的存储关系与逻辑关系没有关联,数据结构中需要一个指针存放数据元素的地址,通过指针去查找相应的数据元素
-
*抽象数据类型 * 数据类型:是指一组性质相同值的集合,以及定义再此集合的一些操作的总称
c语言中按照取值不同,数据剋下可以分为2类:
原子类型:不可分解的基础数据类型,如:整型,浮点型,字符形
结构类型:由若干类型组合而成可以在分解,比如整型数组就是由若干整型数据组成
- 抽象数据类型:抽象,是抽取出事物具有的普遍性的本质. 它是抽出问题的特征而忽略非本质的细节,是对具体事物的一个概括. 抽象是一种思考问题的方式,它隐藏繁杂的细节,只保留实现目标所必需的信息.抽象数据类型: 是指一个数学模型以及定义在该模型上的一组操作; 例如,我们在编写计算机绘图软件系统时,经常会使用到坐标. 也就是说,会经常使用x,y来描述横纵坐标. 而在3D系统中,Z深度就会出现. 既然这3个整型数字是始终出现在一起. 那就可以定义成一个Point的抽象数据类型. 它有x,y,z三个整型变量. 这样开发者就非常方便操作Point 数据变量. 抽象数据类型可以理解成实际开发里经常使用的结构体和类; 根据业务需求定义合适的数据类型以及动作.
二、算法
算法解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作.
算法与数据结构是相辅相成的,程序设计 = 数据结构 +算法
算法的特性:
- 输入输出:算法要有开始与结束的入口出口
- 有穷性:算法的执行步骤是有限次数的,不会无限循环,且每一步都可以在可以接受的时间内完成
- 确定性:算法的输出结果要有确定行,不可产生差异浮动
- 可行性:算法每一步必须是可以执行的,都可通过有限次数执行完成
算法设计要求:
- 正确性
算法的正确性是指算法至少应该具有输入,输出和加工处理无歧义性,能正确反映问题的需求,能够得到问题的正确答案; 正确分为4个层次:
- 算法程序没有语法错误;
- 算法程序对于合法的输入数据能够产生满足要求的输出结果;
- 算法程序对于非法的输入数据能够得出满足规格说明的结果;(4).算法程序对于精心选择的,甚至刁钻的测试数据都有满足要求的输出结果;
-
可读性: 算法设计的另一个目的是为了便于阅读,理解和交流; 可读性高有助于人们理解算法,晦涩难懂的算法往往隐含错误,且不容易发现并且难于调试和修改;注意, 并不是代码越少,算法越好,可读性是算法好坏的很重要的标志!
-
健壮性:一个好的算法还应该能对输入数据的不合法的情况做出合适的处理.考虑边界性,也是在写代码经常要做的一个处理; 比如,输入时间或者距离不应该是负数;
- 健壮性: 当输入数据不合法时,算法也能做出相关处理,而不是产生异常和莫名其妙的结果;
-
时间效率高和存储量低:时间越低,消耗内存越小,算法也就越优秀