Jay jay
文章28
标签2
分类0
数据结构学习之线性表

数据结构学习之线性表

线性表(linear list)

定义:

线性表是具有相同数据类型的n(n>=0)个数据元素有限序列,其中n为表长,当n=0时线性表示一个空表。若用L来命名线性表,则其一般表示为:L=(a1,a2,a3,…,an)

例如:英文字母表就是一个线性表

注意:

  • a1是线性表中的“第i个”元素线性表的位序

  • a1是表头元素;an是表尾元素

  • 除了第一个元素外,每一个元素有且仅有一个直接前驱

    除最后一个元素,外每个元素有且仅有一个直接后继

  • 位序从开始,数组下标从开始

  • 同一线性表中的元素必定具有相同的特性,即属于同一数据对象,相邻数据元素间序偶关系

线性表的基本操作

  1. 从无到有,从有到无:
    • InitList(&L):初始化表。构造一个空的线性表L,分配内存空间。
    • DestroyList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间
  2. 增、删:
    • ListInsert(&L)插入操作。在表L中的第i个位置上插入指定的e元素
    • ListDelete(&L)删除操作,删除表L中的第i个位置的元素,并用e返回删除的元素的值
  3. 查:
    • LocateElem(L,e)按值查找操作,在表L中查找具有给定关键字值的元素
    • GetElem(L,i)按位置查找操作,获取表中第i个位置的元素值
  4. 其他常用操作:
    • Length(L)求表长,返回线性表L的长度,即L中元素个数
    • PrintList(L)输出表中所有元素
    • Empty(L)判断表是否为空

注意:

  1. 对数据的操作(记忆思路)–创销,增删改查
  2. 对c语言的定义————<返回值类型> 函数名(<参数1类型> 参数一 <参数二> 参数二 ……)
  3. 实际开发中,根据实际需求定义其他基本操作
  4. 函数名和参数形式、命名都可改变
  5. 什么时候传入引用,及修改原值,传值和传地址参数的区别

顺序表(顺序存储):

定义:用顺序存储的方式实现线性表。

顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系

​ · 由存储单元的邻接关系来体现

sizeof(Elem Type)函数计算数据元素大小

静态分配实现:

利用数组这种方式实现静态顺序表

typedef struct{               //分配的存储空间大小为MaxSizen*sizeof(Elemtype)
ElemTpye data[Maxsize]; //用静态的“数组”存放数据元素,Elemtype可以根据需要更换为实际数据类型
int length; //顺序表的当前长度
} Sqlist; //顺序表的类型定义(静态模式)(SQ:sequence顺序、序列)

int类型的静态顺序表实现:

#include<stdio.h>
#define Maxsize 10 //定义最大长度
typedef struct{
int data[Maxsize]; //用静态的数组存放数据元素
int length; //数据表的长度
} SqList; //顺序表的类型定义
void InitList(SqList &L){
// for (int i = 0; i < L.length;i++) //如果不所有的元素赋值为零,则内存中遗留的脏数据则会赋值给data,编译器不同值不同,有的编译器会赋初始值为零
// {
// L.data[i] = 0; //也可省略这一步,因为长度已经规定为零用基本操作访问数据,不会访问到“脏数据”
// }
L.length = 0; //顺序表初始长度为零
}
int main()
{
SqList L; //声明一个顺序表
InitList(L); //初始化顺序表
//非法访问查看“脏数据”
for (int i = 0; i < Maxsize;i++){
printf("data[%d]:%d \n", i, L.data[i]);
}
return 0;
}

查看是否有脏数据:

可以看到存在脏数据

静态顺序表由于是刚开始就要定义存储空间是静态的,当静态顺序表满了我们直接放弃治疗不扩充

静态顺序表的缺点是浪费存储空间

基本操作:

  1. 插入数据元素:

    void ListInsert(SqList &L,int i,int e){   //基本操作在L的位序为i的位置插入元素e
    for (int j = L.length; j >= i;j--){ //将第i个元素之后的元素向后移动
    L.data[j] = L.data[j - 1]; //注意位序、数组下标的关系,并从后面的元素依次移动
    }
    L.data[i - 1] = e; //在i处放置元素e
    L.length++; //长度增加
    }

    由于这样的做法无法给用其他人调用的体验有反馈值,加上当数据元素满了时没有错误反馈所以改进为以下代码:

    bool ListInsert(SqList &L,int i,int e){   //基本操作在L的位序为i的位置插入元素e
    if(i<1||i>L.length+1) //判断i的范围是否有效
    return false;
    if(L.length>=Maxsize) //当存储空间满了不能插入
    return false;
    for (int j = L.length; j >= i;j--){ //将第i个元素之后的元素向后移动
    L.data[j] = L.data[j - 1]; //注意位序、数组下标的关系,并从后面的元素依次移动
    }
    L.data[i - 1] = e; //在i处放置元素e
    L.length++; //长度增加
    return true;
    }

    插入操作的时间复杂度计算:

    最好情况:新插入的元素插入到表尾,不需要移动元素吗,i=n+1,循环0次,最好时间复杂度为O(1)

    最坏情况:新插入元素在表头,将原有的n个元素全向后移动,i=1,循环n次最坏时间复杂度O(n)

    平均情况:

    新元素插入有(n+1)种选择,即插入每个位置的概率都是 p= 1/(n+1)
    i =1 ,循环n次
    i =2 ,循环n-1次

    i =n+1, 循环 0次

    平均循环次数: = np+(n-1)p+…+1*p = n/2
    即 平均时间复杂度 = O(n)
  2. 顺序表的删除:

    bool ListDelete(SqList &L,int i,int &e){  //此处e不加引用法将第i个元素的值传出去
    if(i<1||i>L.length) //判断i的范围是否合法
    return false;
    e = L.data[i - 1]; //将第ige位置的元素值赋给e
    for (int j = i; j < L.length;j++){ //将第i个位置后的元素向前移
    gailuxiangt L.data[j - 1] = L.data[j]; //注意位序、数组下标的关系并从前面的元素以次移动
    } //要与插入是分辨开
    L.length--; //线性表长度减一
    return true;
    }

    删除操作的时间复杂度:

    最好情况:删除表尾元素不需要移动其他元素 i=n,循环0次;最好时间复杂度O(1)

    最坏情况:删除表头元素,需要将后续的n-1个元素全部向前移动,i=1,循环n-1次最坏时间复杂度O(n)

    平均情况:

    删除操作有n种选择,即删除每个位置的概率都是 p= 1/n
    i =1 ,循环n-1次
    i =2 ,循环n-2次

    i =n, 循环 0次

    平均循环次数: = (n-1)p+…+1*p = (n-1)/2
    即 平均时间复杂度 = O(n)

    插入和删除结果:

  3. 顺序表的查找:

    • 顺序表的按位查找:时间复杂度:O(1),由于存放是连续的所以可以实现随机存取

      Elemtype Getelemtype(SqList L,int i){
      return L.data[i - 1];//数组元素下标从零开始所以i-1
      }
      //加入判断i的值是否合法
      bool Getelem(SqList L,int i,Elemtype e){
      if(i<0||i>L.length)
      return false;
      else{
      e = L.data[i];
      return true;
      }
      }
    • 顺序表的按值查找:

      int LocateElem(SqList L,ElemType e){
      for (int i = 0; i < L.length;i++){
      if(L.data[i]==e)
      return i + 1;
      }
      return 0;
      }
      //以int类型为例则为
      int LocateElem(SqList L,int e){
      for (int i = 0; i < L.length;i++){
      if(L.data[i]==e)
      return i + 1;
      }
      return 0;
      }

      在进行元素对比时两个结构体不能直接用“==”比较,必须分别比较结构体内的每一个分量是否相等,也可以实现一个相等判断的方法来调用。

      时间复杂度:(问题规模n=L.length)

      1. 最好情况:目标元素在表头,循环一次,最好时间复杂度=O(1)
      2. 最坏情况:目标元素在表尾,循环n次,最好时间复杂度=O(n)
      3. 平均情况:
      假设目标元素出现在每个位置的概率都是 p= 1/n
      目标元素在第1位 ,循环1次
      目标元素在第2位,循环2次

      目标元素在第n位, 循环n次

      平均循环次数: = 1*(1/n)+2*(1/n)+...+n*(1/n)=(n+1)/2
      即 平均时间复杂度 = O(n)

两种查找的测试结果:

还有一些简单的基本操作:

//清空顺序表
void ClearList(SqList &L){
L.length = 0;
}
//获得顺序表的长度
int GetLength(SqList L){
return L.length;
}
//输出表中所有元素
void PrintList(SqList L){
for (int i = 0; i <L.length;i++){
printf("L.data[%d]:%d \n", i, L.data[i]);
}
}
//判断表是否为空
bool IsEmpty(SqList L){
if(NULL==*L.data)
return true;
else
return false;
}

动态分配实现

//动态分配顺序表
#define Initsize 10 //顺序表的初始长度
typedef struct{
Elemtype *data; //动态分配数组指针
int MaxSize; //顺序表的最大容量
int lenght; //当前顺序表的长度
}SeqList; //顺序表的定义类型(动态分配)

关键点:动态分配和释放空间

c语言中利用malloc、free函数来动态申请和释放空间,使用malloc、free函数需要使用这个库文件#include<stdlib.h>

c++则使用new、delete来动态申请和释放空间

int类型动态顺序表的实现:

#define Initsize 10  //顺序表的初始长度
typedef struct{
int *data; //动态分配数组指针
int MaxSize; //顺序表的最大容量
int lenght; //当前顺序表的长度
}SeqList; //顺序表的定义类型(动态分配)

//动态初始化
void InitList(SeqList &L){
//用malloc函数来申请一片连续的存储空间
L.data = (int *)malloc(sizeof(int) * Initsize);
L.lenght = 0;
L.MaxSize = Initsize;
}

void IncreaseSize(SeqList &L,int len){
int * p = L.data;
// printf("%d\n", p[0]);
L.data=(int *)malloc(sizeof(int)*(L.MaxSize+len));//新申请的空间
for(int i=0;i<L.lenght;i++){
L.data[i] = p[i]; //将原来的数据保存到新空间
}
// printf("%d\n", L.data[0]);
L.MaxSize=L.MaxSize+len;
free(p);//释放原来的内存空间
}

int main(){
SeqList L;//声明一个顺序表
InitList(L);//初始化顺序表
//向顺序表中插入数据。。。
L.lenght = 10;
L.data[0] = 1;
L.data[8] = 2;
// printf("%d\n",L.data[0]);
IncreaseSize(L, 3);
printf("增加后原来数据还在否,L.data[0]=%d,L.data[8]=%d\n", L.data[0],L.data[8]);
printf("增加后的空间长度:%d",L.MaxSize);
return 0;
}

基本操作:

插入操作、删除操作、查找操作:

由于动态分配创建的顺序表可以用类似访问数组的方式访问所以其插入操作、删除操作、查找操作与静态方法相同其访问过程是通过定义的指针指向申请空间的首地址,然后通过计算类型空间大小,指出数组下标对应的空间地址,在使用malloc函数申请空间时已经 强制定义了类型,不同类型用的空间大小不同。

顺序表的特点:

优点:

  1. 随机访问,即可以在O(1)时间内找到第i个元素的值(数组下标访问)
  2. 存储密度高,每个节点只存储数据元素

缺点:

  1. 拓展容量不方便(即使使用动态分配的方式实现,拓展的时间复杂度比较高)
  2. 插入删除操作不方便,需要移动大量元素

链表(链式存储):

​ 链表是物理存储单元上非连续、非顺序的存储结构。与我们之前学习过的数组同为存储结构,区别是数组是连续的、顺序的存储结构。

链式存储结构的特点是:

​ 用一组任意的存储单元存储线性表的数据元素(可连续可不连续)。

结点:

​ 数据元素的存储映像:数据元素的本身信息和后继元素的逻辑关系。

单链表:

是链表中最简单的单向链表,每一个结点除了存放数据元素外,还要存储指向下一个结点的指针

信息域:存储数据元素信息

指针域:存储直接后继存储位置的域,其中存储的信息位指针或链

从上图可知:

  • 单链表的每一个节点里面有一个信息域(element)和一个指向下一个节点的指针(next)。
  • 查找一个节点是从第一个节点(head)找起。

优点:

  1. 不要求大片连续空间
  2. 改变容量方便

缺点:

  1. 不可随机存取
  2. 要耗费一定空间存放指针

代码定义:

typedef int Elemtype;
//定义结点:
// struct Lnode{
// Elemtype data;
// struct Lnode *next;
// };
// typedef struct Lnode LNode;
// typedef struct Lnode * LinkList;
//以下的定义等价与上面注释的定义
typedef struct Lnode{ //定义单链表结点类型
Elemtype data; //定义结点存放一个数据元素
struct Lnode *next; //定义一个指向下一个结点的置置
}LNode, *LinkList;
/*LinkList为指向结构体LNode的指针,Linklist与LNode*两者等价
利用struct Lnode *p=(struct LNode *)malloc(sizeof(Struct LNode))
申请空间,增加一个新结点并用p指针指向这个结点
在书写时并不方便,所以使用typedef关键字为数据类型重命名
其格式:typedef <数据类型><别名>
LNode*这中强调的时返回一个结点
LinkList这是强调定义的是一个单链表
*/

LNode*这中强调的时返回一个结点
LinkList这是强调定义的是一个单链表

初始化:

//初始化一个空的单链表(不带头结点)
bool InitList(LinkList &L){
L = NULL;//空表,暂时还没有任何结点 防止脏数据
return true;
}
//初始化一个带头结点的单链表
bool InitListD(LinkList &L){
L = (Lnode *)malloc(sizeof(Lnode));
if(L==NULL)
return false;
L->next = NULL;
return true;
}
//判断单链表是否为空
bool IsEmpty(LinkList L){
// if(L == NULL)
// return true;
// else
// return false;
return (L == NULL);
}
//判断带头结点的单链表是否为空
bool IsEmptyD(LinkList L){
if(L->next==NULL)
return true;
else
return false;
}

首元结点:是指链表中存储第一个数据元素的结点

头结点:是在首结点之前附设的一个结点,其指针域指向首结点,其数据域可不存储任何信息,也 可存储与数据元素类型相同的其他附加信息,在理解时可以将头结点看作时第0个结点。

头指针:是指向链表中第一个节点的指针,有头结点,则头指针所指结点为线性表的头结点,若无 头结点则头指针所指结点为线性表的首元结点

带头结点写代码方便,不带头结点对于第一个数据节点的后续节点的处理需要用不同的代码逻辑,对空表和非空表的处理需要用不同的代码逻辑

单链表的插入和删除:

插入操作:

  1. 按位序插入:(在表L中的第i个位置插入指点元素e,首先找到第i-1个结点,将新结点插入其后)

    • 带头结点:(在i=1的位置插入,可以把头结点看成是第0个结点用基本逻辑就可实现)

      //带头结点插入
      bool Listsert(LinkList &L,int i,Elemtype e){
      if(i<1)
      return false;
      Lnode *p; //指针p指向当前扫描到的结点
      int j = 0; //当前p指向第几个结点
      p=L; //L指向头结点,头结点是第0个结点(不存放数据)
      while(p!=NULL&&j<i-1) //循环找到第i-1个结点推出循环
      {
      p = p->next;
      j++;
      }
      //return InsertNextNode(p, e); //用后插代替下面的代码调用函数
      if(p==NULL) //i值不合法超过当前链长
      return false;
      Lnode *s = (Lnode *)malloc(sizeof(Lnode));
      s->data = e;
      s->next = p->next;//顺序不能反,否则会使链断
      p->next = s;//将结点s连到p之后
      return true; //插入成功
      }

      分析:

      当i=1(插在表头),首先查找到头结点然后申请一个新的结点,新结点的数据域赋值为e,然后将新结点的next指针指向头结点中的next所指的位置;然后头结点的next指针指向新申请的结点,这样就可以将链连起来。若顺序颠倒在断链后续结点无法找到。因为只在头结点后插入,所以循环跳过时间复杂度为O(1).

      当i=3(插在表中),首先在循环中查找到第i-1个结点,在第i-1个结点插入新结点

      当i等于表尾,在循环中当p指向要插入位置的前一个结点时由于j++后不满足即推出循环找到插入位置的前一个结点,p不为空然后将新结点连入链表中,时间复杂度为O(n)n为表长

      当i大于链表的最大长度+1则会由于p!=NULL这个条件跳出循环和p=NULL返回false

    • 不带头结点

      //不带头结点的插入:
      bool ListSertD(LinkList &L,int i,Elemtype e){
      if(i<1)
      return false;
      if(i==1){ //由于不带头结点其逻辑和一般插入逻辑不同所以单独操作
      Lnode *s = (Lnode *)malloc(sizeof(Lnode));
      s->data = e;
      s->next = L;
      L=s; //头指针指向新结点
      return true;
      }
      Lnode *p = L;//指针p指向当前扫描的结点,指向第1个结点,不是头结点无头结点
      int j = 1; //当前p指向第几个结点
      while(p!=NULL&&j<i-1) //循环找到第i-1个结点
      {
      p = p->next;
      j++;
      }
      //return InsertNextNode(p, e);//调用后插函数代替下面的代码
      if(p==NULL) //i值不合法超过当前链长
      return false;
      Lnode *s = (Lnode *)malloc(sizeof(Lnode));
      s->data = e;
      s->next = p->next;//顺序不能反,否则会使链断
      p->next = s;//将结点s连到p之后
      return true; //插入成功
      }

      分析:

      当i=1(无头结点插在表头),不带头结点则插入删除第1个元素时只需要更改头指针L,后续更改插入与带头结点的逻辑相同

      结论:

      推荐使用带头结点的写代码方便,不带头结点不方便(考试两种都要考察)

  2. 指定结点的后插:

    //指定结点的后插操作:
    bool InsertNextNode(Lnode *p,Elemtype e){
    if(p==NULL)
    return false;
    Lnode *s = (Lnode *)malloc(sizeof(Lnode));
    if(s==NULL) //内存分配失败(某些情况可能发生)
    return false;
    s->data = e; //用结点s保存数据元素e
    s->next = p->next;
    p->next = s; //将结点s连到p之后
    return true;
    }

    所以前面找到第i-1个结点后就可以调用后插函数了

  3. 指定节点的前插:

    //指定结点的前插操作:
    bool InsertPriorNode(LinkList L, Lnode *p, Elemtype e) {
    Lnode *q=L;//q指针查找p的前驱结点
    while(q->next!=p){
    q = q->next;
    }
    InsertNextNode(q, e);
    return true;
    }
    //传入头指针即可利用循环查找到所有结点信息找到指定结点的前一个结点新结点插到其后连起来
    //其时间复杂度为O(n)

    //偷天换日法实现前插其时间复杂度为O(n):
    bool InsertPriorNodeT(Lnode *p,Elemtype e){
    if(p==NULL)
    return false;
    Lnode *s = (Lnode *)malloc(sizeof(Lnode));
    s->next = p->next;
    p->next = s;
    s->data = p->data;
    p->data = e;
    return true;
    }

删除操作(针对与带头结点的删除):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值

找到第i-1个结点,将其指针指向第i+1个结点

  1. 按位序删除:

    //按位序删除结点(带头结点)
    bool ListDelete(LinkList &L,int i,Elemtype &e){
    if(i<1)
    return false;
    Lnode *p;//指针p指向当前扫描到的结点
    int j = 0;//当前p指向第几个结点
    p = L;//指向头结点
    while(p!=NULL&&j<i-1)
    {
    p = p->next;
    j++;
    }
    if(p==NULL)
    return false;//当前传入的i值不合法
    if(p->next->next==NULL)
    return false;//第i-1个节点之后以无其他结点
    Lnode *q = p->next;
    e = q->data;
    p->next = q->next;
    free(q);
    return true;
    }//最好时间复杂度O(1) 最坏、平均时间复杂度:O(n)
  2. 指定结点删除:

    //删除指定结点(两种:1、传入头指针查找删除   2、偷天换日法)
    bool DeleteNode(Lnode *p){
    if(p==NULL)
    return false;
    Lnode *q = p->next;//q指向*p的后继结点
    p->data = p->next->data;//和后继结点交换数据
    p->next = q->next; //将*q结点从链中断开
    free(q); //释放后继结点的存储空间
    return true;
    } //时间复杂度为O(1);

    //传入头指针
    bool DeleteNodeT(LinkList &L,Lnode *p)
    {
    if(L==NULL||p==NULL)
    return false;
    Lnode * q = L;
    while(q->next!=p)
    q = q->next;
    if(q->next->next==NULL)
    {
    free(p);
    q->next = NULL;
    }
    q->next = p->next;
    free(p);
    return true;
    }

    这段代码存在一个问题:当p是最后一个节点q则为空指针,就只能用从表头开始一次寻找p的前驱的方法删除时间复杂度为O(n),这也是单链表的局限性,无法逆向检索,有时不太方便。

查找操作(带头结点):

  1. 按位查找操作:获取表L中第i个位置的元素的值

    //按位查找(带头结点)
    Lnode * GetElem(LinkList L,int i){
    if(i<0)
    return NULL;
    Lnode *p;
    int j = 0;
    p = L;
    while ( p!=NULL && j < i){
    p = p->next;
    j++;
    }
    return p;
    }
    /*当i=0时直接返回头结点
    当i大于链表的实际长度返回一个空指针NULL
    平均时间复杂度为O(n)
    上节课的找到第i-1个结点更加方便
    将常用代码封装成函数可以避免重复代码,简介易维护
    */
  2. 按值查找操作:在表中查找具有给定关键字值的元素

    //按值查找(带头结点):
    Lnode * LocateElem(LinkList L,Elemtype e){
    Lnode *p = L->next;
    while(p!=NULL&&p->data!=e){
    p = p->next;
    }
    return p;
    }
    /*
    平均时间复杂度O(n)
    判断的时候Elemtype是结构体,结构体判断不能直接用等于号,写函数判断
    */
  3. 求表长:函数代码与查找类似故加在此处

    //求表长
    int Length(LinkList L){
    int len = 0;
    Lnode *p=L;
    while(p->next!=NULL){
    p = p->next;
    len++;
    }
    return len;
    }

​ 由于单链表不具备随机访问的特性,只能一次扫描,所以三种基本操作的时间复杂度为O(n),在编写代码时要注意边界值的处理。

单链表的建立方法:

  1. 尾插法:(含有一个尾指针)

    第一步初始化一个单链表、第二步每一次取一个数据元素,插入到表尾、表头

    尾插法建立单链表:初始化单链表,设置变量记录链表长度,利用while循环每次取一个数据元素插入到length+1个位置然后length++;这种插入方式每次都从开头开始遍历,其时间复杂度就为O(n的平方)

    我们可以设置一个尾指针来减小时间复杂度,每一次将新的数据元素插在尾指针后边

    //尾插法建立单链表
    LinkList List_Tailnsert(LinkList &L){//正向建立单链表
    int x;//设置elemtype为整形
    L = (LinkList)malloc(sizeof(Lnode));//建立头结点 ----->初始化一个空表
    Lnode *s, *r = L;//r为尾指针
    scanf("%d", &x);
    while(x!=9999) //9999只是一个结束的标志
    { //在r结点之后插入元素x
    s = (Lnode *)malloc(sizeof(Lnode));
    s->data = x;
    r->next = s;
    r = s; //r指向新表结尾 --------->保证r永远指向最后一个结点
    scanf("%d", &x);
    }
    r->next = NULL; //尾指针制空
    return L;
    }
    //时间复杂度为O(n)
  2. 头插法:

    第一步初始化一个单链表,第二部在while循环中获取一个数据元素,插入到头结点之后

    时间复杂度仍然为O(n)

    //头插法建立单链表
    LinkList List_Headinsert(LinkList &L){//逆向建立单链表(输入的是逆向排序
    Lnode *s;
    int x;
    L = (LinkList)malloc(sizeof(Lnode));//创建头结点
    L->next = NULL; //初始为空链表
    scanf("%d", &x); //输入结点的值
    while(x!=9999){ //结束标志
    s = (Lnode *)malloc(sizzeof(Lnode));//创建新结点
    s->data = x;
    s->next = L->next;
    L->next = s; //将新节点插入表中,L为头指针
    scanf("%d", &x);
    }
    return L;
    }
    //养成良好的编程习惯只要初始化单链表,都先把头指针指向NULL,若不指向空指针则会指向内存中遗留的脏数据影响代码的运行

    头插法可以运用与链表的逆置

双链表:

​ 单链表无法逆向检索,有时候不太方便,于是乎产生了双链表,它可进可退,但存储密度更低一些

​ 只需要在单链表的基础上在增加一个指针域来指向前驱结点,prior(先前的),双链表中的一个结点叫 Dnode.

typedef struct Dnode{  //定义双链表结点类型
Elemtype data; //数据域
struct Dnode *prior, *next;//前驱指针和后继指针
} Dnode, *DLinklist;

初始化双链表(带头结点):

//初始化双链表
bool InitDLinkList(DLinklist &L){
L = (Dnode *)malloc(sizeof(Dnode)); //分配一个头结点
if(L==NULL) //内存不足分配失败
return false;
L->prior = NULL;//头结点的prior永远指向NULL
L->next = NULL;//头结点之后暂时还没有结点
return true;
}

是否为空(带头结点):

//是否为空(带头结点)
bool Empty(DLinklist L){
if(L->next==NULL)
return false;
else
return true;
}

双链表的插入:

//双链表的插入(后插实现)
bool InsertNextDnode(Dnode *p,Dnode *s){
if(p == NULL || s == NULL)
return false;
s->next = p->next;//将结点*s插入到结点*p之后
if(p->next!=NULL) //如果p结点有后继结点执行
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
//需要注意修改指针的顺序,顺序不当会使后续结点信息丢失

在进行前插操作时我们可以找到给定结点的前前驱结点然后在后插实现

双链表的删除操作:

//双链表的删除操作(删除给定结点的后继节点)
bool DeleteNextDnode(Dnode *p){
if(p==NULL)
return false;
Dnode *q = p->next; //找到p的后继结点
if(q==NULL) //p没有后继结点
return false;
p->next = q->next;
if(q->next!=NULL) //q结点不是最后一个结点
q->next->prior = p;
free(q); //释放结点空间
return true;
}

销毁一个双链表:

//销毁一个双链表
void DestoryDList(DLinklist &L){
//循环释放各个数据结点
while(L->next!=NULL) //直到头结点后再无其他结点
DeleteNextDnode(L);
free(L); //释放头结点
L = NULL; //释放头指针
}

双链表的遍历:

//双链表的遍历(ergodic)
void ergodic(DLinklist L){
Dnode *p = L->next;
//后向遍历
while(p){
p = p->next;
//做相应的处理
}
//循环完成p已经指向尾部
//向前遍历
while(p){
//对结点p做相应的处理
p = p->prior;
}
//向前遍历跳过头结点
while(p->prior!=NULL)
{
//对p结点做相应的处理
p = p->prior;
}
}
//求长度设置变量统计,按值查找的话只需要在处理时进行值对比其时间复杂度O(n)

循环链表

只需要在单链表和双链表的基础上做一些小改动即可

循环单链表:

​ 只需将单链表尾结点的next指针指回头结点即可,在初始化时也要将头结点的next指针指向自己,判断循环单链表是否为空只需要判断(L->next==L)即可,若成立则为空的循环单链表,在判断一个结点是否为循环单链表的尾结点时只需要判断这个结点的next指针指向是否为头结点。

特点:

​ 从一个结点出发可以找到其他任何一个结点,由于尾结点的next指针指向头结点,而单链表就没有这种特性。

​ 普通的循环单链表从头结点找到尾部结点的时间复杂度为为O(n)要遍历整个链表,但是如果我们将循环单链表的指针一开始就指向尾部结点查找时其时间复杂度就为O(1),由于可循环这样也是可以访问整个循环单链表的。当循环单链表指针指向尾结点时在对表尾进行插入删除操作时需要修改循环单链表的指针指向。

//初始化一个循环单链表
bool InitListX(LinkList &L){
L = (Lnode *)malloc(sizeof(Lnode));//分配一个头结点
if(L==NULL) //内存不足分配失败
return false;
L->next = L; //头结点next指向头结点
return true;
}

//判断循环单链表是否为空
bool Emptyx(LinkList L){
if(L->next==L) //循环单链表尾结点的next指针指向头结点,与初始化中对应
return true;
else
return false;
}

//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,Lnode *p){
if(p->next==L) //循环单链表和单链表的主要区别就在尾结点的处理
return true;
else
return false;
}
//对尾结点的处理一定要掌握。

循环双链表

​ 只需要将普通双链表头结点的prior指针指向表尾结点,表尾结点的next指针指向头结点即可。

//初始化空的循环双链表
bool InitDLinkListx(DLinklist &L){
L = (Dnode *)malloc(sizeof(Dnode)); //分配一个头结点
if(L==NULL) //分配失败
return false;
L->prior = L; //头结点的Prior指向头结点
L->next = L; //头结点的next指向头结点
return true;
}

//判断循环双链表是否为空
bool Emptyx(DLinklist L){
if(L->next==L)
return true;
else
return false;
}

//判断结点p是否为循环双链表的表尾结点
bool isTailx(DLinklist L,Dnode *p){
if(p->next==L)
return true;
else
return false;
}
循环双链表的基本操作:
  1. 插入操作:

    //插入操作:在p结点之后插入s结点
    bool InsertNextDnodex(Dnode *p,Dnode *s){
    s->next = p->next; //将结点*s插入到结点*p之后
    p->next->prior = s;
    s->prior = p;
    p->next = s;
    return true;
    }
  2. 删除操作:

    //双链表的删除
    bool DeleteDnodex(Dnode *p,Dnode *q){ //删除p的后继结点q
    p->next = q->next;
    q->next->prior = p;
    free(q);
    return true;
    }

在操作循环单(双)链表时注意的地方:

  1. 如何判断表空
  2. 如何判断结点p是否尾头结点
  3. 如何在表头、表尾、表中插入或删除一个结点

静态链表:

​ 分配一整个连续的内存空间,各个结点集中安置。每一个结点中有一个数据域和一个下标域。用数组方式实现的链表,先申请一片连续的空间,空间内部每一个结点的逻辑关系并不是和数组的位序关系确定而是由每一个结点中的游标来确定相互间的关系。位序为0的数组结点为头结点,而游标对应的就是数组下标。(数组内部混乱)

静态链表

特点

优点:增删操作不许要移动大量元素

缺点:不能随机存取,只能从头结点开始一次往后找,容量固定不变

顺序表和链表的比较:

  1. 逻辑结构:

    • 顺序表和链表都时线性结构
  2. 物理结构/存储结构:

    • 顺序表采用顺序存储,链表采取链式存储

    • 顺序表支持随机存取,存取密度高,但大片连续空间分配不方便,改变容量不方便

      链表离散小空间分配,改变容量方便,但不支持随机读取,只能从表头开始查找,由于每个结点要存储指针所以存取密度低

  3. 数据的运算/基本操作:

    创销增删改查在上文已经详细记录

如何选择这两种方式:

  1. 当需要的线性表表长难以估计、经常需要增减删除元素时选择链表
  2. 表长可预估、查询搜索操作较多时选择顺序表

本文结束

数据结构学习

数据结构学习

数据结构

数据结构在学什么?

​ 用程序代码把现实世界的问题信息化,并利用计算机高效的处理这些信息并且创造价值。

数据

​ 数据是信息的载体,是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别和处理的符号集合,数据是计算机程序加工的原料。对于计算机来说就是0 1。

​ 数据元素:是数据的基本单位,通常作为一个整体进行考虑和处理。由数据项组成,数据项是构成数据元素不可分割的最小单位。

数据结构、数据对象:

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

​ 数据对象:是具有相同性质的数据元素的集合,是数据的一个子集。

数据结构的三要素(要关注的三个方面):

  1. 逻辑结构:数据元素之间的逻辑关系

    数据的逻辑结构:

    • 集合:各个元素同属一个集合,别无其他关系。

    • 线性结构:数据元素之间是一对一的关系;除了第一个元素,所有元素都有唯一前驱;除了最后一个元素,所有元素都有唯一后继。

    • 树形结构:数据元素之间是一对多的关系。

    • 图状结构(网状结构):数据元素之间是多对多的关系。

  2. 物理结构(存储结构):用计算机表示数据元素的逻辑关系

    数据的存储结构:(存储结构会影响存储空间的分配方便程度)

    顺序存储(物理上必须是连续的):

    • 顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,它们之间的关系由存储单元的邻接关系体现。(分配一段连续的存储空间)

    非顺序存储(物理上可以是离散的,对空间的分配更方便):

    • 链式存储:逻辑上相邻的元素在物理位置上可以不相邻,借助指针来表示元素间的逻辑关系。
    • 索引存储:在存储元素信息的同时建立索引表,索引表中的每项称索引项其一般形式是(关键字,地址)
    • 散列存储:根据元素的关键字直接计算出该元素的存储地址,称哈希存储(hash)

    不同的存储结构影响都数据的运算速度。

  3. 数据运算:

    ​ 施加在数据上的运算的定义和实现。运算的定义是针对逻辑结构的,指出运算的功能,运算的实现是针对存储结构的,指出运算的具体操作步骤。

数据类型、抽象数据类型

数据类型(一个值的集合和定义在此集合上的一组操作的总称):

  1. 原子类型(值不可再分割):bool、int
  2. 结构类型(值可在分割为若干成分的数据类型):结构体

抽象数据类型(adt):是抽象数据组织及与之相关的操作。

算法

程序=数据结构+算法:数据结构是把现实世界的问题信息化,将信息存入计算机,同时 还要实现对数据结构的基本操作。算法是处理这些信息以解决实 际问题。

算法的特性:

  1. 有穷性:一个算法必须在执行有穷步之后结束,且每一步都在有穷时间内完成。(算法 有穷程序可无穷
  2. 确定性:对于相同的输入只能有相同输出。
  3. 可行性:算法中描述的操作可以通过已经实现地基本运算执行有限次来实现。
  4. 输入:一个算法中有0个或多个输入,输入来自某特定的对象集合。
  5. 输出:一个算法有一个或多个输出。输入输出有某种特定关系的量。

好算法的特性:

  • 正确性:算法能够正确的解决求解问题。
  • 可读性:良好的可读性帮助人们理解。
  • 健壮性:输入非法数据时算法能适当的做出反应或进行处理,不会产生莫名结果。
  • 高效率与低存储需求:执行速度快时间复杂度低,不废内存空间复杂度

算法的效率度量:

  1. 时间复杂度(算法时间开销):随问题n的增大算法执行的时间的增长率和f(n)的增长率相同

    让算法先运行,事后统计。问题:

    • 和机器的性能有关,超级计算机和单片机
    • 和编程语言有关,越高级的语言执行效率越低
    • 和编译程序产生地机器指令质量有关
    • 有些算法是不能事后统计的如导弹控制算法

    排除算法本身无关的外界因素事前估计:

    事前预估算法时间开销(时间time)T(n)与问题规模n的关系:

    简单代码可以一行一行的数和计算。

    忽略表达式的某些部分:去掉低阶部分只保留高阶部分,系数化1: “T(n)=O(高阶)”

    • 加法规则:T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))

    • 乘法规则:T(n)=T1(n)×T2(n)=O(f(n))×O(g(n))=O(f(n)×g(n))

    • 常见算法复杂度排序:O(1)<O(log2n)<O(n)<O(nlogn)<O(n方)<O(n立方)<O(2的n次方)<O(n阶乘)<O(n的n次方) 魔法:常对幂指阶(上升)

    好几千行代码的处理(只需考虑最深层循环):

    • 顺序执行的代码只会影响常数项,可以忽略(只关注循环)

    • 只需调循环中的一个基本操作分析他的执行次数与n的关系即可

    • 嵌套循环为n的几次嵌套次方:多层嵌套循环只需关注最深层循环循环了几次

    • 最好时间复杂度:算法在最好情况下的时间复杂度,算法尽可能的达到最小值

    • 最坏时间复杂度:算法在最坏情况下的时间复杂度,算法尽可能的达到最大值

    • 平均时间复杂度:指算法在所有可能的情况下,按照输入势力以等概率出现时,

      ​ 算法计算量的加权平均值

    在计算算法时间复杂度时一般考虑最坏和平均复杂度

  2. 算法的空间复杂度:(解决空间开销与问题规模n之间的关系)

    • 算法的空间复杂度:(与时间复杂度表示法类似也是O表示法保留最高阶数)

    S(n)=O(f(n)) :S表示为“Space”

    当S(n)=O(1)表示算法原地工作,算法所需内存空间为常量,算法所需空间与问题n的规模无关

    通过定义的变量来计算空间复杂度,还有递归调用函数调用也需要计算空间

    魔法:空间复杂度=递归调用的深度

python学习之函数

python学习之函数

函数定义

函数:完成特定功能的一个语句组,通过调用函数名来完成语句组的功能,可以反馈结果,提供不 同的参数实现对不同数据的处理。

自定义函数由用户自己编写,系统自带函数有python中内嵌的函数和标准库函数

使用函数的目的:降低编程难度,代码重用。

函数定义语句:

  1. 函数关键字:函数以关键字def开头,后接函数名和圆括号()
  2. 函数名:必循遵循标识符的命名规则,最好有意义增加可读性
  3. 参数:必须放在圆括号中间,成为形式参数(形参),形参是可选的,可以有一个也可以有多个,多个参数间用‘,’分隔
  4. 函数说明:函数第一行的语句可以选择性地使用文档字符串,用于存放函数说明
  5. 函数内容:以冒号起始并且缩进
  6. 返回值:使用“return [表达式]”结束函数,返回一个值给调用方,若没有renturn返回值则默认返回None,当函数返回多个值时其本质是把这些值作为一个元组返回。
  7. 函数体:当函数体为pass语句,表示什么工作也不做,如抽象类中的抽象函数
def 函数名([参数1,参数2,...]):
"""函数说明"""
函数体

以sum函数定义为例

例子:

def myHello():
return "hello,everone!"

函数有自己的成员,也可以为函数动态增减删除成员,在函数外部删除函数内部定义成员是无效的。

函数调用:

函数调用时需要指出函数名称,并传入相应的函数调用时传入的参数称实参,默认情况下传入的实参必须等于定义时形参个数,顺序一制

函数调用顺序:

  1. 执行函数调用前语句
  2. 执行函数调用,运行被调用函数内语句,并返回结果
  3. 执行函数调用后的语句

函数调用的两种方法:

  1. 调用函数传入实参
  2. 把函数对象赋给变量

以sum函数调用为例

定义一个求梯形面积函数并调用

#求梯形面积
def echelonArra(top,bottom,height):
area=1 / 2 * (top+bottom) *height
return area
if __name__ == '__main__':
t=3.6;b=6.2;h=4.5
area1=echelonArra(t,b,h) #第一种调用
print("area1=",area1)
area2=echelonArra
print("area2=",area2(t,b,h)) #第二种调用

结果

函数参数传递

python中的对象分为不可变对象(数字、字符串、元组)和可变对象(列表、字典、集合)

  1. 实参为不可变对象,函数调用时将实参的值复制一份给形参,在函数调用中修改形参时不会影响函数外面的实参
  2. 实参为可变对象,函数调用时将实参引用复制给形参,在函数调用中修改形参时会影响函数外面的实参

实参为不可变对象调用:

def Swap(x,y):
print("交换前:x={},y={}".format(x,y))
x,y=y,x
print("交换后:x={},y={}".format(x,y))

if __name__ == '__main__':
a=6;b=5
Swap(a,b)
print("a={},b={}".format(a,b))

实参为可变对象调用:

def changeList(myList):
myList.append(4)

if __name__ == '__main__':
list=[1,2,3]
print("调用前list:",list)
changeList(list)
print("调用后list:",list)

参数类型

必须参数

要求调用时传入的实参个数、顺序和函数定义时形参的个数、顺序完全一致

def myAdd(x,y,z):
return x+y+z
a=1;b=2;c=3
print("结果:",myAdd(a,b,c))

关键字参数

在函数调用时使用形参作为关键字来确定传入的参数值,允许函数调用时实参的顺序与函数定义时形参的顺序不一致

def stuInfo(sno,sname):
return "学号:"+sno+"\n"+"姓名:"+sname

print(stuInfo(sname="假的",sno="66666"))

默认参数

调用函数时,如果没有传递实参,则会使用函数定义时赋予的参数的默认值

def stuInfo1(sno,sname,age=18):
return "学号:"+sno+"\n"+"姓名:"+sname+"\n"+str(age)

print(stuInfo1(sname="假的",sno="66666"))
print(stuInfo1(sname="假的",sno="66666",age=25))

不定长参数

在实际使用中有可能需要一个函数能处理比当初声明时更多的参数,这种参数成为不定长参数

两种形式:

  • *args:将接受的多个参数放在一个元组中
  • **args:将显示赋值的多个参数放入字典中
def addFunc(x,y,*args):
res=x+y
for k in args:
res +=k
return res

def func(**args):
for key,value in args.items():
print("{},{}".format(key,value))

if __name__ == '__main__':
print("调用结果:",addFunc(1,2,3,4,5))
func(新发明1="高铁",新发明2="扫码支付")

参数传递的序列解包

参数传递的序列解包针对的是实参,有*或**两种形式,加了后会将列表、元组、字典等迭代对象中的元素分别传递给形参中的多个变量

将列表、元组、字典中的值分别迭代取出赋值给形参

特殊函数

1、匿名函数

​ 匿名函数是指没有函数名的简单函数,只可以包含一个表达式,不允许包含其他复杂语句,表达式的结果就是函数的返回值

​ 一般格式:

lambda [arg1[,arg2,...,argn]]:expression

arg1,arg2,…:形参,可以没有,可以有一个或多个

expression:表达式

默认情况下调用匿名函数时传入实参个数与顺序同样要和匿名函数定义时形参个数顺序一致

map()会根据提供的函数对指定序列做映射。

第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。

2、递归函数

​ 如果一个函数在函数体中直接或间接的调用自身,那么这个函数就是递归函数,在执行的过程中可能会返回以再次调用该函数

​ 函数a调用函数a自身为直接递归,函数a调用b,函数b在函数体中调用a则称简介递归

  • ​ 递归函数求前n项的和:

    #递归求前n项的和
    def fac(n):
    if n==1:
    return 1
    else:
    return fac(n-1)+n
    if __name__ == '__main__':
    print("前{}项的和为:{}".format(9,fac(9)))

    在递归时需要注意的两点:
    1、递归函数就是在函数里调用自身
    2、递归函数必须有一个明确的递归结束条件,称递归出口,否则造成死循环
    3、前n-1项的和再加第n项,当n=1时为递归出口返回一
  • 使用递归求斐波那契数列前20项和

#递归求斐波那契数列20项的和
def fib(n):
if n==1 or n==2:
return 1
else:
return fib(n-1)+fib(n-2)
if __name__ == '__main__':
#求前20项:
sum=0
for i in range(1,21):
sum=sum+fib(20)
print("前20项的和为{}".format(sum))

递归函数的优点:
1、递归函数使代码看起来更整洁优雅
2、可以用递归将复杂任务分解成更简单的子问题
3、使用递归比使用一些嵌套迭代更容易
递归函数缺点:
1、递归逻辑很难调试跟进
2、递归调用的代价高效率低,占用了大量的内存和时间
递归层数太多时不适合使用递归函数完成程序

3、嵌套函数

​ 嵌套函数是指在一个函数(外函数)中定义了另一个函数(内函数),称为内函数,嵌套函数中的内函数只能在外函数中调用,不能在外函数外面直接调用

#嵌套函数
def outfunc():
def infunc():
print("我是内函数")
print("我是外函数")
infunc()

def outerFunc(x):
def innerFunc(y):
return x+y;
return innerFunc #调用内函数

if __name__ == '__main__':
outfunc()
# infunc() #此句报红内函数在外部不可见出现语法错误

print("方法一调用结果:",outerFunc(3)(4)) #调用时参数从最外层开始传递(最外层层参数)(内层参数)
wai=outerFunc(3) #调用外函数,传递外函数参数
print("方法二调用结果",wai(4)) #间接调用内函数传递参数

4、装饰器

​ 装饰器是python中的一个比较特殊的功能,是用来包装函数的函数,装饰器可以是程序代码更简洁

使用的情况:

  • 将多个函数中的重复代码拿出来整理成一个装饰器,对每个函数使用装饰器,从而实现代码的重用
  • 对多个函数的共同功能进行处理

定义装饰器的一般格式:

def decorator(func):
pass
@decorator
def func():
pass

其中decorator为装饰器。@decorator为函数装饰器的修饰符。func为装饰器的函数对象参数,装饰器可以返回一个值、一个函数、一个装饰器或其他对象

如:

# 装饰器
def decorator(func):
print("I am in deco.")
func()
return "deco return value."

@decorator
def funca():
print("I am in func!")
print(funca)

执行原理:
1、装饰器deco()的参数为一个函数对象func
2、在函数前使用@deco修饰相当于将函数对象func作为参数调用装饰器deco(func)
3、func的值为调用装饰器deco(func)返回的结果

使用装饰器修改网页文本格式:

def deco(f):
def modify_text(str):
return "<string>"+f(str)+"</string>"
return modify_text

@deco
def texfun(str):
return str

if __name__ == '__main__':
print(texfun("text"))

5、带参数的装饰器

​ 装饰器除了默认函数对象参数还可以定义带参数的装饰器,带来更多的灵活性

def DECO(args):
def deco(func):
def call_func(x,y):
print("%d %s %d="%(x,args,y),end=' ')
return func(x,y)
return call_func
return deco

@DECO('&')
def andfunc(x,y):
return x&y

@DECO('|')
def orfunc(x,y):
return x|y

if __name__ == '__main__':
print(andfunc(5,6))
print(orfunc(5,6))

若一个函数有多个装饰器装饰,则称为多重装饰器,多重装饰器的执行顺序是:先执行后面的装饰器,在执行前面的装饰器。

变量作用域

python中不同位置定义的变量决定了这个变量的访问权限和作用范围:

  • 局部变量和局部作用域L(Local):包含在def关键字定义的语句块中即函数中定义的变量,局部变量在函数结束就消失了,调用时创建新的,结束时消失
  • 全局变量和全局作用域G(Global):在模块的函数外定义的变量,在模块顶层声明的变量具有全局作用域,从外部看全局变量是模块对象的属性,全局作用域的作用仅限于单个模块文件内
  • 闭包变量和闭包作用域E(Enclosing):定义在嵌套函数的外函数内、内函数外的变量,闭包变量作用域为嵌套函数内定义它的位置开始的整个函数内
  • 内建变量和内建作用域B(Built-in):系统内固定模块里定义的变量一般预定义在内建模块内的变量

按照L→E→G→B的顺序查找,局部变量和全局变量使用的多,内建变量使用的最少

局部变量只能在其声明的函数内使用,全局变量在整个模块内访问

修改变量作用域的关键字:

  1. global:加上global即可将变量设置为全局变量
  2. nonlocal:加上即可将嵌套函数中的内函数局部变量变为闭包变量

结束

python语言基础学习(二)

python语言基础学习(二)

程序结构

顺序结构

顺序向下写

选择结构

  • 单分支

    if 表达式:
    语句块
  • 二分支

    if 表达式:
    语句块1
    else:
    语句块2
  • 多分支

    if 表达式1: 
    语句块1
    elif 表达式2:
    语句块2

    [else:
    语句块n+1]

    练习:使用if多分支语句实现简单的算术运算,•支持+、-、*、/运算:

    if __name__=='__main__':
    input_op=input("请输入运算:(操作数何操作符之间用空格分隔)")
    x,op,y=input_op.split()
    if op=='+':
    z=float(x)+float(y)
    elif op=='-':
    z=float(x)-float(y)
    elif op == '*':
    z=float(x)*float(y)
    elif op == '/':
    if y==0:
    print("分母不能为零输入错误!!!")
    else:
    z=float(x)/float(y);
    print("{}{}{}={}".format(x,op,y,z))

    结果

循环结构

while 语句

while 表达式:
语句块
[else:
else子句语句块]

for语句

for 变量 in 序列或迭代对象:
语句块
[else:
else子句语句块]

三个在循环语句中使用的关键字:

break语句:退出循环
continue语句:跳过该次循环
pass:空语句(不做任何处理)

练习 使用while语句计算1~100的和(for循环时可以使用range函数):

if __name__=='__main__':
i=0
s=0
while i<=100:
s=s+i
i=i+1
print("while循环:",s)
s1=0
for a in range(1,101):
s1=s1+a
print("for循环:",s1)

结果

练习 输出九九乘法表:

if __name__=='__main__':
for i in range(1,10):
for a in range(1,i+1):
if(i*a>=10):
str=" "
else:
str=" "
print("%d*%d=%d"%(a,i,i*a),end=str)
print(" ")

结果

组合数据

列表

  • 创建列表

    1、使用[ ]运算符创建列表:
    list1 = []
    list2 = [1.25,21.06,0.3,4.7,58.1]
    list3 = ["石油","汽车","建筑","IT"]
    list4 = ['Alice’, 18, 'Beth’, 19]
    2、使用list()函数创建列表
    >>>list1 = list()
    >>>list(("李白","杜甫","白居易"))
    ['李白', '杜甫', '白居易']
    >>> list("素质重于能力重于学历")
    ['素', '质', '重', '于', '能', '力', '重', '于', '学', '历']
    >>> list(range(5))
    [0, 1, 2, 3, 4]

  • 访问列表

    1、索引访问

    索引访问

​ 2、切片访问

切片访问

  • 遍历列表

    carList = ["奔驰", "大众", "福特", "宝马", "奥迪", "雪佛兰"]
    for car in carList:
    print(car,end=' ')

    结果

  • 添加列表元素

    添加方法如下:
    (1) list.append(newItem)
    (2) list.insert(index, newItem)
    (3) list.extend(seq)
    (4) list[len(list):] = newList

    调用

  • 修改列表元素

    修改

  • 删除列表元素

    (1) del list[index]:删除索引为index的元素。
    (2) list.pop():删除列表末尾的元素。
    (3) list.pop(index):删除索引为index的元素。
    (4) list.remove(item):删除列表元素item。
    (5) list.clear():删除列表中所有元素。
    (6) list[::] = []:对指定范围的列表元素进行删除。

    尝试使用删除

    尝试2

    • 复制列表

      1、直接复制 list_copy = list1

      2、浅拷贝 使用list.copy(只对第一层实现深拷贝)

      3、深拷贝 使用copy模块,copy.deepcopy()

if __name__ == '__main__':
a=[[1,2],[3,4],280]
c=a.copy()
b=copy.deepcopy(a)
print(id(a[0]))
print(id(b[0]))
print(id(c[0]))
print('——' *10)
print(id(a[2]))
print(id(b[2]))
print(id(c[2]))

拷贝分析

  • 删除列表

  • 列表运算
+、*、in、not in
关系运算符:规则是从两个列表的第1个元素开始比较,如果比较有结果则结束;否则继续比较两个列表后面对应位置的元素。
注:对比字符串运算(一致)
  • 列表统计

    (1) len(list):返回列表list中的元素个数。
    (2) max(list):返回列表list中元素的最大值。
    (3) min(list):返回列表list中元素的最小值。
    (4) sum(list):返回列表list中所有元素的和。
    (5) list.count(key):返回关键字key在列表中的出现次数。

  • 列表查找与排序

    1、查找函数
    List.index(key)
    2、排序函数
    list.sort():对列表list中的元素按照一定的规则排序。
    list.reverse():对列表list中的元素按照一定的规则反向排序。
    sorted(list):对列表list 中的元素进行临时排序,返回副本。

元组

元组(Tuple)是写在()之间用逗号隔开的元素集合,元组创建后其中的元素不能修改

  • 元组创建

    使用()运算符创建元组

    tuple1=() #创建空元组
    tuple2=(1,8,24,65,125)#元素为数字
    tuple3=("计算机","生物信息")#元素为字符串
    tuple4=("华为","5564654",666)#元素为数字和字符串混合

    使用tuple()函数创建元组:tuple(sequence)

    tuple("理想是人生的太阳")
    ('理', '想', '是', '人', '生', '的', '太', '阳')
    tuple(range(1,6))
    (1, 2, 3, 4, 5)
    tuple(["莎士比亚","托尔斯泰","但丁","雨果","歌德"])
    ('莎士比亚', '托尔斯泰', '但丁', '雨果', '歌德')
  • 元组访问

    1. 通过元组名访问元组

    2. 通过tuple[index]访问指定索引为index的元组元素

    3. 元组切片(start:end:step)

    4. 遍历元组:for 变量 in 元组名

      测试

  • 元组复制和删除

    1. 支持赋值语句直接复制

    2. del删除元组

      测试

  • 元组运算

    元组运算包括+、in/not in 及关系运算符等,使用方法和列表中类似

    wanyue_tuple=("柳永","晏殊")
    haofang_tuple=("陆游","苏轼")
    ci_author_tuple=wanyue_tuple+haofang_tuple
    print("宋朝词人",ci_author_tuple)
    print("李清照是宋朝词人吗?","李清照" in ci_author_tuple)
    print("岳飞 出现三次的元组:",("岳飞")*3)
    print("wanyue_tuple <ci_author_tuple?",wanyue_tuple<ci_author_tuple)

    测试

  • 元组统计

    1. max()

    2. min()

    3. sum()

    4. count()

    5. len()

      等和列表操作类似

      pelltuple=(0,1,2,5,12,29,70,169,408,985)
      print("最大值:",max(pelltuple))
      print("最小值:",min(pelltuple))
      print("所有元素的和:",sum(pelltuple))
      print("元素%d在元组中出现的次数:%d.次"%(5,pelltuple.count(5)))
      print("元组中的元素个数:%d个."%(len(pelltuple)))

测试

字典:

用{}标识创建,字典是一个无序的“键(key):值(value)”对集合,键(key)必须使用不可便类型,如字符串、数字等,值(value)可以是简单数据或组合数据多种不同类型,在同一个字典中键(key)必须是唯一的,值可以不唯一

  • 字典创建

    使用{}运算符创建字典

    使用dict()函数创建:

    (1)dict(**kwarg):以关键字创建字典。**kwarg为关键字

    (2)dict(mapping,**kwarg):以映射方式构造字典,mapping为元素的容器

    (3)dict(iterable,**kwarg):以可迭代对象方式构造字典,iterable为可迭代对象

    测试

  • 字典访问

    通过字典名访问字典,通过“dict[key]”或”dict.get(key)”房屋指定元素

    dict.itms():以列表形式返回字典中所有的“键/值”对,每个“键/值”对以元组的形式存在

    dict.keys():以列表的形式返回字典中的所有键

    dict.values():以列表形式返回字典中所有的值

    访问

    for循环遍历字典中所有元素

    dict={"猪肉":10,"羊肉":20,"牛肉":30}
    for key,value in dict.items():
    print(key,'/',value,end=" ")

    测试

    字典元素的添加、修改、删除

    添加:

    dict[key]=value #key不存在

    修改:

    dict[key]=value #key已存在

    删除:

    del dict[key]:删除关键字为key的元素

    dict.pop(key):删除关键字为key的元素

    dict.popitem():随机删除字典中的元素

    dict.clear():删除字典中所有元素

    测试

  • 字典复制和删除

    赋值语句进行复制:源改新改

    copy函数:源改新不改

    deepcopy模块进行复制

    del语句进行字典删除

    测试

    这里和列表的拷贝类似

集合(set):

在{}之间,用逗号分隔无序且不重复的元素集合,不可以为集合创建索引或执行切片(slice)操作

没有键(key)可以用来获取集合中元素的值。

  • 集合创建

    python中可以使用{}运算符创建集合

    set()函数创建集合,()可以传入字符串、列表、元组或range()函数的返回结果,使用list作为set函数参数可以将列表中的重复元素干掉

    测试

  • 集合访问

    使用集合名访问集合

    for循环遍历集合中的元素

    set.add(item)添加集合元素

    set.update(item)更新修改集合元素

    删除集合元素:

    set.remove(item):删除指定元素item

    set.discard(item):删除指定元素item

    set.pop:随机删除集合中的元素

    set.clear:清空集合列表

    测试

  • 集合复制和删除

    复制:

    ​ 赋值语句复制

    ​ copy()函数赋值(浅复制)

    ​ deepcopy()模块赋值(深复制)

    删除:

    ​ del语句删除

    测试

  • 集合运算

    (1) set1.union(set2) 或 set1 | set2:并集运算。

    (2) set1.intersection(set2) 或 set1 & set2:交集运算。

    (3) set1. difference(set2) 或 set1 - set2:差集运算。

    (4) set1. issubset(set2) 或 set1 < set2:子集运算。

    (5) item in set 或item not in set:成员运算。

    测试

  • 集合统计

    max()函数、min()函数、sum()函数、len()函数

    测试

嵌套组合数据

​ 当组合数据中的元素为组合数据时,称为嵌套组合数据如嵌套列表中的元素是:列表、元素、字典、集合等,嵌套字典中的元素是:列表、字典、元组、集合。

python语言基础学习

python语言基础学习

python基础

python中的数据类型:

数字

整形:

Python中的整形不限制大小:

  • 十进制 10 int() 其他类型转十进制整型

  • 二进制 0b1010或0B1010 bin()其他类型转二进制

  • 八进制 0O12 oct()其他类型转八进制

  • 十六进制 0xa hex()其他类型转十六进制

​ 练习:

if __name__=='__main__':
a=123
print(a)
print("二进制:",bin(a))
print("八进制:",oct(a))
print("十六进制:",hex(a))

浮点型

  • 十进制浮点数:1080.00 %f (格式化输出符)

  • 科学计数法浮点数:1.08e3 %e (格式化输出符)

  • python支持大约17位的精度和范围从-308到308的指数(双精度)

  • 不分单精度和双精度

​ 练习:将华氏温度转为摄氏温度:

if __name__ == '__main__':
f=100.00
c=(f-32.00)/1.8
print("摄氏度:%f"%c)

​ 浮点型的比较运算:

​ 比较运算:浮点数可以直接比较大小 由于精度的原因执行以下代码时会输出else的结果

a = 0.2
b = 0.1
if((a + b) == 0.3):
print("(a + b) == 0.3")
else:
print("(a + b) != 0.3")

复数类型

complex(实部, 虚部)

例:c1=1.2+5.3j为complex(1.2, 5.3)

布尔类型

取值True、False,在python中0、空字符串表实False

类型函数

练习:

x=25
y=30
print(int(x))
print(float(x))
print(bool(x))
print(complex(x))
print(complex(x, y))

常用数学函数:

组合数据

字符串:

  • 成对的单引号(‘)或双引号(“)括起来

  • 如:”中国制造”,’Python’

  • 成对的三引号(‘’’’’’)或(“‘)创建跨多行的字符串

  • 也可以是多行注释

字符串索引

练习:

if __name__=='__main__':
a='success'
print(a[1],a[2:],a[:4],a[2:4],a[:])

结果

字符串创建函数:

  • str() 转义

  • repr() 原样

运行图:

字符串运算

​ 比较时判断的是字符串的编码大小

字符串函数

字符串格式化输出方式:

  • print(‘{0}, {1}.’.format(‘Hello’,’world’))
  • print(‘{}, I am {}.’.format(‘Hello’,’Python’))
  • print(‘{0} {1}, {1} is a new prgramming language.’.format(‘Hello’,’Python’))
  • print(“网站名:{name}, 地址 {url}”.format(name=”name”, url=”http://www.xxxx.xx/"))

字典格式化:

site = {"name": "xxxx", "url": "http://www.xxxx.cn/"}
print("网站名:{name}, 地址 {url}".format(**site))

列表格式化:

list = ['world','python']
print('hello {names[0]}, I am {names[1]}.'.format(names=list))
print('hello {0[0]}, I am {0[1]}.'.format(list))

练习:判断输入的手机号码是否正确

if __name__=='__main__':
while (1):
a = input("请输入手机号:")
if len(a)!=11:
print("手机号长度不合法,请重新输入!")
else:
if a.isdecimal():
print("输入正确!")
break;
else:
print("输入不正确!请重新输入!")

结果

常用转义字符

常量和变量

  • 常量:Python中没有专门定义常量的方式,通常使用大写变量名表示。其本质还是变量。

  • 例:PI = 3.14

  • 变量:

    变量的值可以变,类型也可以变。

    当用变量的时候,必须给这个变量赋值。

    如果只声明一个变量而没有赋值,则Python认为这个变量没有定义。

运算符

内置函数

  • 内置函数(Built-In Functions,BIF)是Python的内置对象类型之一,封装在标准库模块_ builtins _中 如:input(),print()

特殊内置函数

本次学习结束