恋上数据结构与算法初深

652 阅读8分钟

数据结构的节本术语

  • 数据:程序的操作对象,用于描述客观事物, 数据的特点: 可输入到计算机,可被计算机处理。 
  •  数据项:一个数据元素由若干数据项组成 数据元素: 组成数据的对象的基本单位 数据对象:性质相同的数据元素的集合(类似数组) 。
  •  结构:数据元素之间不是独立的,存在特定的关系,这些关系便是结构 数据结构:指数据对象的数据元素之间的关系 如一个结构体便是一个数据项```struct Teacher{ //一种数据结构

    //声明一个结构体类型struct Teacher{     //一种数据结构   
     char *name;     //数据项--名字   
     char *title;    //数据项--头衔   
     int  age;       //数据项--年龄
    };

总结来说: 数据是 包含很多个数据对象,数据对象包含很多 数据元素,数据元素包含很多数据项 

存储结构 

我们一般分享数据结构有两种视角

逻辑结构

  •  集合结构 线性结构(一对一的关系),如:数组,队列,栈,字符串,
  •  树形结构(一对多的关系)
  •  图形结构(多对多的关系)
  • 总结:逻辑结构描述的是数据与数据之间的逻辑关系

物理结构

  • 线性结构最终需要物理结构来存储到内存中
  • 链式存储:不需要连续开辟一段内存空间

算法比较

  • 算法的特点
  1. 输入输出
  2. 有穷性
  3. 可行性
  4. 确定性

算法设计要求

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

算法的时间复杂度:一般用大O表示法,只保留最高阶

  • 用常数1取代运行时间中所有常数 3->1 O(1)

  • 在修改运行次数函数中,只保留最高阶项 n^3+2n^2+5 -> O(n^3)

  • 如果在最高阶存在且不等于1,则去除这个项目相乘的常数 2n^3 -> n^3

时间复杂度术语

  • 常数阶
  • 线性阶
  • 平方阶
  • 对数阶
  • 立方阶
  • nlog阶
  • 指数阶(不考虑) O(2^n)或者O(n!)

/* 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次
}

//y = y+1; 执行1次
void add(int y){
    y = y+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);   
 /*    
 i = 0,执行n次   
 i = 1,执行n-1次    
 i = 2,执行n-2次   
  所以便是一个等差数列 
 */
}

//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次          
                 }       
             } 
       }
}

时间复杂度小结:

O(1) < 0(log n) < O(n) < O(nlog n)  <  O(n^2) < O(n^3) < < O(2n) < O(n!) < O(n^n)

算法的空间复杂度

通过计算算法所需的存储空间实现,计算公式:S(n) = n(f(n)),其中n为问题的规模,f(n)为语句关于n所占储存空间的函数

空间复杂度计算因素:

1. 寄存本身的指令

2. 常数

3. 变量

4. 输入

5. 对数据进行操作的辅助空间

列子: 数组逆序,将一维数组a中的n个数逆序存放在原数组中.

void test{   
    int n = 5;   
    int a[10] = {1,2,3,4,5,6,7,8,9,10};   

    //算法实现(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)  
     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;
}

线性表

顺序表

非空的线性表和线性结构特点:

  • 存在唯一的一个被称作“第一个”的数据元素;
  • 存在唯一的一个被称作“最后一个”的数据元素;
  • 除了第一个之外,结构中的每个数据元素均有一个前驱;
  • 除了最后一个之外,结构中的每个数据元素都有一个后驱;

eg:

#include <stdio.h> 
#include "stdlib.h"
#include "math.h"
#include "time.h"

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

//1.1 顺序表初始化Status InitList(Sqlist *L){
//如果要修改链表,则需要传指针    //为顺序表分配一个大小为MAXSIZE 的数组空间
    L->data =  malloc(sizeof(ElemType) * MAXSIZE);
    //存储分配失败退出    if(!L->data) exit(ERROR);    
    //空表长度为0    
    L->length = 0;  
      return OK;
}

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

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

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

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

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

//1.7 获取顺序表长度ListEmpty元素个数 */
int ListLength(Sqlist L){  
  return L.length;
}

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

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

int main(int argc, const char * argv[]) { 
    Sqlist L;  
    Sqlist Lb;   
    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, 1, j);    
    }   
    printf("插入数据L长度: %d\n",L.length);

    //1.3 顺序表取值  
    GetElem(L, 5, &e);   
    printf("顺序表L第5个元素的值为:%d\n",e);   

    //1.4 顺序表删除第2个元素
    ListDelete(&L, 2);
    printf("顺序表删除第%d元素,长度为%d\n",2,L.length);

    //1.5 清空顺序表
    iStatus = ClearList(&L);
    printf("清空后,L.length = %d\n",L.length);   

     //1.6 判断List是否为空  
    iStatus=ListEmpty(L);   
    printf("L是否空:i=%d(1:是 0:否)\n",iStatus);   

     //1.7 TraverseList
    for(int j=1; j <= 5;j++){   
         iStatus = ListInsert(&L, 1, j);    
    }   
    TraverseList(L);    

    return 0;
}