数据结构与算法【基础】+线性结构部分

一、数据结构核心名词

  • 数据

程序的操作对象,用于描述客观事物
特点:
1、可以输入到计算机
2、可以被计算机运行

  • 数据对象

性质相同的数据元素的集合(类似于数组)

  • 数据元素

组成数据的对象的基本单位

  • 数据项

一个数据元素由若干个数据项组成

  • 结构

数据元素之间不是独立的,存在特定的关系,这些关系即是结构

  • 数据结构

指的是数据对象中的数据元素之间的关系

代码中的体现:

#include <stdio.h>
//声明一个结构体类型
struct Person{      //一种数据结构
    char *name;     //数据项--名字
    char *title;    //数据项--职称
    int  age;       //数据项--年龄
};
int main(int argc, const char * argv[]) {
    struct Person p1;     //数据元素;
    struct Person tArray[10]; //数据对象;
    p1.age = 25;       //数据项
    p1.name = "小C";    //数据项
    p1.title = "程序猿";  //数据项
    return 0;
}
数据结构-基本数据单位.png

二、逻辑结构与物理结构区别

逻辑结构(数据和数据之间的逻辑关系)

  • 集合结构

所有的数据都属于同一集合
🌰:同学A、B、C、D都属于一班,他们之间没有先后顺序的,是平等的。

  • 线性结构

数据和数据是 一对一
🌰:数组、线性表、字符串(由n个字符组成)、栈(先进后出)、队列(先进先出)

  • 树形结构

数据和数据是 一对多

  • 图形结构

数据和数据是 多对多

物理结构(数据最终存储在内存当中)

  • 顺序存储结构

开辟一段连续的内存空间,然后依次存储进去

  • 链式存储结构

不需要提前开辟一个连续的内存空间

顺序存储结构与链式存储结构的区别与优缺点

区别
1、链式存储结构的地址不一定是连续的,但顺序存储结构的内存地址一定是连续的;
2、链式存储结构适用于比较频繁地插入、删除、更新元素,而顺序存储结构适用于频繁的查询时使用

优缺点
1、空间上:顺序比链式节约空间
2、存储操作上:顺序支持随机存储,方便操作
3、插入和删除上:链式的要比顺序的方便(因为插入的话顺序也很方便,问题是顺序的插入要执行更大的空间复杂度,包括一个从表头索引以及索引后的元素后移,而链式是索引后,插入就完成了)

三、算法设计要求

算法 就是解决特定问题求解的描述,在计算机中表现为指令的有限序列,并且每个指令表示一个或多个操作

算法的特性

  • 输入输出
  • 有穷性
  • 确定性
  • 可行性

设计要求即衡量算法的要求

  • 正确性
  • 可读性
  • 健壮性
  • 时间效率高和储存量低

四、算法时间复杂度

大O表示法

1、用常数1取代运行时间中所有常数
🌰:3 -> O(1)
2、在修改运行次数函数中,只保留最高阶层
🌰:n3+2n2+5 -> O(n3)
3、如果在最高阶存在且不等于1,则去除这个项目相乘的常数
🌰:2n3 -> O(n3)

下面我们拿些🌰看看:

4.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次
    sum = (1+n)*n/2;            //执行1次
    sum = (1+n)*n/2;            //执行1次
    printf("testSum2:%d\n",sum);//执行1次
}

4.2、线性阶

//n+n=2n   O(n)
void add2(int x,int n){
    for (int i = 0; i < n; i++) {  //执行n次
        x = x+1;                   //执行n次
    }
}
//1+n+1+n+1=2n+3   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次
}

4.3、对数阶

//log2 n+1     O(logn)
void testA(int n){
    int count = 1;         //执行1次
    while (count < n) {    
        count = count * 2; //2的x次方等于n  
                            //x=log2 n次
    }
}

4.4、平方阶

//n+n^2+n^2=2n^2+n    O(n^2)
void add3(int x,int n){
    for (int i = 0; i< n; i++) {      //执行n次
        for (int j = 0; j < n ; j++) {//执行n*n次
            x=x+1;   //执行n*n次
        }
    }
}
//n+(n-1)+(n-2)+...+1 = n(n-1)/2 = n^2/2 + n/2 
//等差数列公式:sn=n(a1+an)/2
//O(n^2)
void testSum4(int n){
    int sum = 0;  
    for(int i = 0; i < n;i++)  
        for (int j = i; j < n; j++) {
            sum += j;
        }
}
//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次
        }
    }
}

4.5、立方阶

//1+n+n^2+n^3+n^3=2n^3+n^2+n+1
//O(n^3)
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次
            }
        }
    }
}

常见的时间复杂度

执行次数函数 术语
12 O(1) 常数阶
2n+3 O(n) 线性阶
3n2+2n+1 O(n2) 平方阶
5log2n+20 O(logn) 对数阶
2n+3nlog2 n+19 O(nlogn) nlogn阶
6n3+2n2+3n+4 O(n3) 立方阶
2n O(2n) 指数阶

O(1) < O(log n) < O(n) < O(nlog n) < O(n2) < O(n3) < O(2n)

五、算法空间复杂度计算

主要考虑算法执行时所需要额辅助空间
计算公式:S(n)=n(f(n))
其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数

问题:数组逆序,将一维数组a中的n个数逆序存放在愿数组中。

#include <stdio.h>
int main(int argc, const char * argv[]) {
    // insert code here...
    int n = 5;
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    //算法实现(1)  空间复杂度为:O(1)
    //只使用到一个辅助
    int temp;
    for(int i = 0; i < n/2 ; i++){
        temp = a[i];
        a[i] = a[n-i-1];
        a[n-i-1] = temp;
    }
    for(int i = 0;i < 10;i++)
    {
        printf("%d\n",a[i]);
    }
    
    //算法实现(2)   空间复杂度为:O(n)
    int b[10] = {0};
    for(int i = 0; i < n;i++){
        b[i] = a[n-i-1];
    }
    for(int i = 0; i < n; i++){
        a[i] = b[i];
    }
    for(int i = 0;i < 10;i++)
    {
        printf("%d\n",a[i]);   
    }
    return 0;
}

衡量一个算法的时候是描述最坏的情况,如果最坏情况一致,那么再比较平均值

六、线性表----关于顺序存储的实现(增删改查)

6.1、顺序存储(逻辑相邻,物理存储地址相邻)

6.1.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{
    ElemType data;
    struct Node *next;
}sqlist;
//1.初始化
Status InitList(sqlist *L){
    L->data = (sqlist)malloc(sizeof(ElemType)*MAXSIZE);
   if(!L->data) return ERROR;
    L->length=0;
    return OK;
}

6.1.2、顺序表的插入

/*
 初始条件:顺序线性表L已存在,1≤i≤ListLength(L);
 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
 */
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;
}

6.1.3、顺序表的取值

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;
}

6.1.4、顺序表删除

/*
 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
 操作结果: 删除L的第i个数据元素,L的长度减1
 */
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;
}

6.1.5、清空顺序表

/* 初始条件:顺序线性表L已存在。操作结果:将L重置为空表 */
Status ClearList(Sqlist *L)
{
    L->length=0;
    return OK;
}

6.1.6、判断顺序表清空

/* 初始条件:顺序线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */
Status ListEmpty(Sqlist L)
{
    if(L.length==0)
        return TRUE;
    else
        return FALSE;
}

6.1.7、获取顺序表长度ListEmpty元素个数

int ListLength(Sqlist L)
{
    return L.length;
}

6.1.8、顺序输出List

/* 初始条件:顺序线性表L已存在 */
/* 操作结果:依次对L的每个数据元素输出 */
Status TraverseList(Sqlist L)
{
    int i;
    for(i=0;i<L.length;i++)
        printf("%d\n",L.data[i]);
    printf("\n");
    return OK;
}

6.1.9、顺序表查找元素并返回位置

/* 初始条件:顺序线性表L已存在 */
/* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int LocateElem(Sqlist 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;
}

6.2、链式存储

6.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;

Status InitList(LinkList *L){
    
    //产生头结点,并使用L指向此头结点
    *L = (LinkList)malloc(sizeof(Node));
    //存储空间分配失败
    if(*L == NULL) return ERROR;
    //将头结点的指针域置空
    (*L)->next = NULL;
    
    return OK;
}

6.2.2、单链表插入

/*
 初始条件:顺序线性表L已存在,1≤i≤ListLength(L);
 操作结果:在L中第i个位置之后插入新的数据元素e,L的长度加1;
 */
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;
}

6.2.3、单链表取值

/*
 初始条件: 顺序线性表L已存在,1≤i≤ListLength(L);
 操作结果:用e返回L中第i个数据元素的值
 */
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;
}

6.2.4、单链表删除元素

/*
 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
 */
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;
}

6.2.5、单链表前插入法

/* 随机产生n个元素值,建立带表头结点的单链线性表L(前插法)*/
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;
        
    }
}

6.2.6、单链表后插入法

/* 随机产生n个元素值,建立带表头结点的单链线性表L(后插法)*/
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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容