重拾算法--随笔小计(1)

426 阅读13分钟

1、前言

好久没有看算法的东西了,最近有点闲把压箱底的书拿出来再仔细品一下,顺便在做点笔记以加深自己的印象,再做点自己的理解产出。看看经过多年的开发工作之后是不是有不一样的感觉了。

2、数据

2.1、数据有关定义介绍

定义:是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。

  • 可以输入到计算机中 --> 说明不是我想要,是计算机想要的样子。
  • 能被计算机程序处理 --> 目的是能被计算机处理,他是计算机的粮食。
  • 是一个集合 --> 数据描述的是能被计算机处理的符号集合,是一个概念,不是具体的数据。
  • 数据的下一层那是什么 --> 数据对象,具有相同性质的一类符号,是数据的下层单位。
  • 数据元素又是啥 --> 具有一定意义的基本单位,就是数据对象的成员。
  • 那数据项呢 --> 描述数据元素具体是啥样的最小的不可分割的单位

来点程序员能看懂的排列,看下面:

数据 
    数据对象A
        数据元素一
            数据项1
            数据项2
        数据元素二
            数据项1
            数据项2
    数据对象B
        数据元素一
            数据项1
            数据项2
        数据元素二
            数据项1
            数据项2

2.2 数据结构

2.2.1 定义

数据结构是相互之间存在一种或多种特定关系的数据元素的集合

  • 体现的是数据元素之间的关系,我们称之为结构。划分为逻辑结构物理结构
  • 逻辑结构 --> 逻辑是说这些元素之间有某种关系
  • 物理结构 --> 物理说的是存储数据元素之间的关系
结构类型 结构名称 元素之间的关系
逻辑结构 集合结构 无任何关系,只是放在一起
线性结构 一对一的关系,每个元素只能找到对应的一个元素
树形结构 一对多的关系,一个元素能找到多个对应的元素元素
图形结构 多对多的关系,元素能互相之间联系
物理结构 顺序存储结构 元素是在一段连续的内存中存储
链式存储结构 元素是通过指针指向下一个元素存在不连续的内存中存储

2.3 数据类型

定义 数据类型:是指一组性质相同的值得集合及定义在此集合上的一些操作的总称。 咋一看,很晦涩。这都是啥。不着急,看我下面介绍C语言中的数据类型就知道了。

  • 原子类型:是不可以再分割的类型,包括整型、实型、字符型等。-->原子(不可以再分割了嘛估计定义这个的人是个物理或者化学的重度爱好者)
  • 结构类型:由若干个类型组合而成,是可以再分割的。如数组
  • 抽象数据类型:是指一个数学模型及定义在该模型上的一组操作。--> 玩过游戏或者写过游戏的人应该都知道,一个英雄就是一个模型,有很多属性还有很多技能,如果不设计成抽象模型很难从计算机角度解释或者计算。

3、算法

3.1、算法定义

  • 算法 --> 特定问题求解步骤的描述,在计算机中解释为指令的有限序列,且每条指令表示一个或多个操作。
  • 我很烦这种刻板的定义,我是这么理解的。算法-->重在,就是一步一步说出来你是怎么解决这个特定问题的。

3.2、算法的特性

  • 输入输出 --> 可以没有输入,但必须有输出,不然就白忙活了。不要输入就能输出那是创造力强。
  • 有穷性 --> 如果是无穷的,那么我跟别人讲我是怎么一步一步做的,要耽误至少两个人的一声。
  • 确定性 --> 给人讲的时候,步骤理解要正,不能让人不确定,不然照着我说的去做就得不到正确的计算结果了。
  • 可行性 --> 就是每一步都是可以执行可以实际操作的。

3.3、算法设计的要求

既然算法都是有一定特点的,那就不能随便一个解决思路都能成为算法。毕竟是计算机要用的,要严谨。

  • 正确性 --> 有点官方,我理解的就是,语法得当、对输入和输出有明确的规格说明,不能什么都能输入,什么都能有结果,最好还有非法操作检查提示。
  • 可读性 --> 算法都是为了解决问题,为了给别人用的。谁都看不懂的就只能自己用,虽然很装逼,但是会没朋友的。
  • 健壮性 --> 就是有很好地容错性,你可以不按我规定的乱输入,但是我会检查非法输入,并提示。
  • 时间效率高和储存率低 --> 就是能一步搞定的不要啰嗦,能只用一个临时变量的不要用多个。

3.4、算法效率的度量方式

3.4.1 时间复杂度

时间复杂度有一个很经典的就是大O阶表示法。大O阶表示法有以下几个推导法则:

  • 1.用常数1取代运行时间中的所有加法常数。
  • 2.在修改后的运行次数函数中,只保留最高阶项。
  • 3.如果最高阶项存在且不是1,则去除去这个项相乘的常数(实际就是把最高阶常数置为1)。

规则看着有点懵,怎么讲,我们来看几段段算法中的代码算一波。下面我们看看一些常见的大O阶,其实这很考验大家的数学功底,尤其是数列,其实是一种函数增长方式。

  • 常数阶
下面这个算法的执行时间是,O(1+1+1)=O(1),都为常数嘛,用1代替。
int sum = 0, n = 100;  //执行 1 次
sum = (1 + n) *n/2;//执行 1 次
printf("%d", sum);//执行 1 次
  • 线性阶
下面这个算法的执行时间是,O(1+(n+1)+n+1)=O(2n + 3)= O(n),保留最高阶 --> O(2n), 最高阶常数置为1 --> O(n)。
void sum(int n) {
    int sum = 0, int i = 0;//执行1次
    for (i = 1; i< =n; i ++) {//执行n + 1次
        sum += i;//执行n次
    }
    printf("sum = %d", sum);//执行1次
}
int sum = 0, n = 100;  //执行 1 次
sum = (1 + n) *n/2;//执行 1 次
printf("%d", sum);//执行 1 次
  • 对数阶
下面这个算法的执行时间是,O(1+(log2n + 1)+log2n)=O(2log2n + 2)= O(2log2n)= O(logn)。
void testLog(int n) {
    int count = 1;//执行一次
    while(count < n) {//执行log2n +1次
        count *= 2;//执行log2n次
    }
}
  • 平方阶
下面这个算法的执行时间是,O(2n^2 + 2n + 3)=O(2n^2)= O(n^2)。
void testSqure(int n) {
    int i,j,sum = 0;//执行1次
    for (i = 1; i <= n; i++) {//执行n + 1次
        for (j = 1; i <= n; j++) { //执行n*(n + 1)次
            sum += 1;//执行n * n 次
        }
    }
    printf("%d", sum);//执行 1 次
}

其他的以此类推,有一个大小排列如下:

O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)

3.4.2 空间复杂度

*空间复杂度指的是算法执行期间需要用到多少个辅助空间,就是除去传入的数据的临时变量个数。

4、线性表

定义 --> ** 零个或多个数据元素的有限序列 ** 既然是逻辑关系是有顺序的,那么就有对应的存储结构

4.1 顺序存储结构

4.1.1 图示

说到顺序存储结构当然是数组了,这个毋庸置疑。我们用一个图来表示

  • 那么当然也就有插入

  • 删除

4.1.2 代码实现

  • 准备条件
#include "math.h"
#include "stdlib.h"

#define MAXSIZE     100
#define OK          1
#define ERROR       0
#define TRUE        1
#define FALSE       0

/* 定义一个数据类型 */
typedef int ElemType;

/* 定义一个函数结果状态 */
typedef int Status;

/* 设计一个顺序表结构 */
typedef struct {
    ElemType *data;
    int length;
}LC_List;
  • 方法实现
//1.1顺序表的初始化
Status InitList(LC_List *L) {
    //为顺序表分配一个大小为MAXSIZE 的数组
    L->data = malloc(sizeof(ElemType) * MAXSIZE);
    //内存分配失败的话退出
    if (!L->data) exit(ERROR);
    //初始化表的长度为0
    L->length = 0;
    return OK;
}

//1.2顺序表的插入
Status ListInsert(LC_List *L, int i, ElemType e) {
    //先判断插入位置的合法性
    if (i < 1 || i > L->length +1) {
        printf("插入的位置非法,请插入合适的位置");
        return ERROR;
    }
    //判断表是不是已经满了
    if (L->length == MAXSIZE) {
        printf("对不起,顺序表已经满了");
        return ERROR;
    }
    //如果插入的位置不在表尾巴,则需要把i位置及后面的依次挪位子,先挪最后面的
    if (i <= L->length) {
        for (int j = L->length-1; j >= i-1 ; j --) {
            L->data[j+1] = L->data[j];
        }
    }
    //将新的元素放在i的位置上
    L->data[i-1] = e;
    //将顺序表的长度更新一下
    ++L->length;
    
    return OK;
}

//1.3 顺序表删除数据
Status ListDelete(LC_List *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];
    }
    //刷新表长度
    L->length --;
    return OK;
}

//1.4 清空顺序表
/* 只要把顺序表的长度置为0 */
Status ClearList(LC_List *L) {
    L->length = 0;
    return OK;
}

//1.5判断顺序表是否为空表
Status ListIsEmpty(LC_List L) {
    if (L.length == 0) {
        return TRUE;
    } else {
        return FALSE;
    }
}

//1.6获取顺序表的长度
Status ListLength(LC_List L) {
    return L.length;
}

//1.7顺序输出List
Status OrderOutPut(LC_List L) {
    int i;
    for (i = 0; i< L.length; i++) {
        printf("%d\n",L.data[i]);
    }
    printf("\n");
    return OK;
}

//1.8查找元素并返回对应的位置
int LocateElement(LC_List L, ElemType e) {
    int i;
    if (L.length == 0) {
        return 0;
    }
    for (i = 0; i< L.length; i ++) {
        if (L.data[i] == e) {
            break;
        }
    }
    
    if (i>=L.length) {
        return 0;
    }
    return i + 1;
}

int main() {
    LC_List L;
//    ElemType e;
    Status iStatus;
    
    //1.1顺序表初始化
    iStatus = InitList(&L);
    printf("初始化L后: L.length = %d\n",L.length);
    
    //1.2顺序表数据插入
    for (int j= 1; j <= 5; j++) {
        iStatus = ListInsert(&L, L.length+1, j);
    }
    printf("插入数据后: L.length = %d\n",L.length);
    
    //1.3顺序输出
    OrderOutPut(L);
    
    //1.4 顺序表删除第3个元素
    ListDelete(&L, 3);
    printf("删除第%d数据后: L.length = %d\n",3,L.length);
    OrderOutPut(L);
    
    //1.5 在第三个位置插入88
    ListInsert(&L, 3, 88);
    printf("插入数据后: \n");
    OrderOutPut(L);
    
    //1.5清空列表
    iStatus = ClearList(&L);
    printf("清空数据后: L.length = %d\n",L.length);
}

4.2 链式存储结构

4.1.1 图示

链式存储结构是用在存储数据的同时还有一个指针域来指向下一个节点,从而实现非连续的内存存储,如下图

  • 链式存储图示,有的会设置一个头结点。头结点的作用就是标记链表起始位置,数据域的值没有实际意义,除非我们想要用它进行某些操作。

  • 链表插入

  • 链表删除

4.1.2 代码实现

  • 准备条件
#include "string.h"
#include "ctype.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"

#define ERROR   0
#define TURE    1
#define FASLE   0
#define OK      1

#define MAXSIZE  100

typedef int Status;
typedef int ElementType;

//定义数据节点
typedef struct Node {
    ElementType data;
    struct Node *next;
}Node;

typedef struct Node * LinkList;
  • 方法实现
//1.1初始化单链表
Status InitSingleList(LinkList *L) {
    //设计头结点,并用L指针指向此头结点。
    *L = (LinkList)malloc(sizeof(Node));
    //存储空间分配失败
    if (*L == NULL) return ERROR;
    //将头结点的指针域置为空。
    (*L)->next = NULL;
    
    return OK;
}

//1.2单链表插入
Status SingleListInsert(LinkList *L, int i, ElementType 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 = (LinkList)malloc(sizeof(Node));
    //数据域赋值
    s->data = e;
    //指针域赋值
    s->next = p->next;
    //上个节点的指针域修改
    p->next = s;
    
    return OK;
}

//1.3 顺序打印单链表
Status OrderOutputList(LinkList L) {
    LinkList p = L->next;
    while (p) {
        printf("%d\n",p->data);
        p = p->next;
    }
    printf("\n");
    return OK;
}

//1.4 单链表取值
Status GetElement(LinkList L, int i, ElementType *e) {
    //用一个节点来记录节点指针移动
    int j;
    LinkList p;
    //赋值节点,指向链表的首地址
    p = L->next;
    j = 1;
    
    while (p && j<i) {
        //p指向下一个节点
        p = p->next;
        ++j;
    }
    
    //容错判断
    if(!p || j > i) return ERROR;
    
    //用e接收节点的数据
    *e = p->data;
    return OK;
}

//1.5 单链表删除元素
Status ListDelete(LinkList *L, int i, ElementType *e) {
    int j;
    LinkList p, q;//用p指向单链表的首节点,用q来记录需要删除的节点。
    p = (*L)->next;
    j = 1;
    
    //查找第i-1个节点,p记录这个节点
    while (p->next && j < (i-1)) {
        p = p->next;
        j++;
    }
    
    //容错判断
    if (!(p->next) || j>(i-1)) {
        return ERROR;
    }
    
    //用q记录即将删除的节点
    q = p->next;
    //改变p的指针域指向
    p->next = q->next;
    //将q节点中的数据给e
    *e = q->data;
    //释放内存
    free(q);
    
    return OK;
}

//1.6单链表头插法
void InsertSingleListHead(LinkList *L, ElementType e) {
    LinkList p, q;                      /* 用一个节点记录移动到节点的位置,q记录新增的节点 */
    p = (*L)->next;

    q = (LinkList)malloc(sizeof(Node)); /* 新建一个节点 */
    if (q == NULL) return;              /* 容错判断 */
    
    q->data = e;                        /* 赋值数据域 */
    q->next = p;                        /* 赋值指针域 */
    
    (*L)->next = q;                        /* 修改头指针指向 */
}

//1.7单链表尾插法
void InsertSingleListTail(LinkList *L, ElementType e) {
    LinkList p, q;                      /* 用一个节点记录移动到节点的位置,q记录新增的节点 */
    p = (*L)->next;
    
    while (p->next) {                   /* 找到尾结点 */
        p = p->next;
    }
    

    q = (LinkList)malloc(sizeof(Node)); /* 新建一个结点 */
    if (q == NULL) return;              /* 容错判断 */
    
    q->data = e;                        /* 赋值数据域 */
    q->next = NULL;                        /* 赋值指针域 */
    
    p->next = q;                        /* 修改头指针指向 */
}

//1.8清除链表
Status ClearSingleList(LinkList *L) {
    LinkList p, q;          /* 用一个节点指针记录指针移动位置, 用另一个节点指针记录下一个节点 */
    p = (*L)->next;
    
    while (p) {             /* 从首节点开始删除节点 */
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL;      /* 头结点指针域指向空 */
    return OK;
}

//1.9获取链表的长度
int ListLength(LinkList L) {
    LinkList p;          /* 用一个节点指针记录指针移动位置, 用一个int变量记录长度 */
    
    int i = 0;
    p = L->next;
    
    while (p) {             /* 从首节点开始遍历链表 */
        p = p->next;
        ++i;
    }
    return i;
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    
    LinkList L;
    
    Status initCode = InitSingleList(&L);
    if (initCode) {
        printf("初始化单链表成功!\n 开始插入数据\n");
    }
    
    for (int i = 1; i <= 5; i++) {
        SingleListInsert(&L, i, i);
    }
    OrderOutputList(L);
    
    InsertSingleListHead(&L, 66);
    printf("头部插入数据:66\n");
    OrderOutputList(L);
    
    printf("在第3个位置插入数据:18\n");
    SingleListInsert(&L, 3, 18);
    OrderOutputList(L);
    
    printf("删除第3个数据\n");
    ElementType e_delete;
    ListDelete(&L, 3, &e_delete);
    OrderOutputList(L);
    
    printf("尾部部插入数据:29\n");
    InsertSingleListTail(&L, 29);
    OrderOutputList(L);
    
    printf("清空链表\n");
    ClearSingleList(&L);
    printf("单链表的长度为:%d\n",ListLength(L));
    
    return 0;
}