20200330-数据结构-初始化

402 阅读11分钟

沿途的风景要比目的地更弯的否

程序的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;
    
}