数据结构-线性表

线性表

 前言

   本文主要介绍线性表的基本概念以及基本运算方式。

   示范样例包括:类C的伪代码实现数据结构运算。其次,还会提供 C++ 和 Python 版本的实际可运行代码示例。

线性表概念

   线性表是由 n(n>0)个数据元素组成的有穷序列,所以 数据元素个数 n 可记为 线性表的 表长。

   特别地,当 n 为 0 时,线性表为空表。

   当 n 不为0时(不是空表),除起始结点为,其余结点有且只有一个直接后驱。除终端节点外,其余结点有且只有一个直接后驱

线性表的基本运算包括   

   1.初始化  

   2.求表长

   3.求表元素(按索引查找)

   4.定位(按值查找,返回索引)

   5.插入

   6.删除

线性表-顺序表

顺序表的存储方式

   表中的结点依次存放在计算机内存中的一组连续的存储单元中,数据元素在线性表的邻接关系决定其在存储空间的存储位置,即其逻辑关系决定其存储关系。我们一般用数组来表示顺序表。

类C实现顺序表结构伪代码(重点放在插入和删除操作)

1. 定义顺序表结构体并初始化一个SeqlLst

Const int Maxsize = 100;    // 定义list存储空间大小
typedef struct{
      DataType data[Maxsize];    
      int length;            // list 长度(实际存储的值得个数)
}SeqList;

SeqList L                    // 初始化一个L

2.求表长-表长直接返回length即可

// 对于顺序表来说,由于结构体中定义了一个length,该length即为存储的实际元素个数

int Length(SeqList L){
    return L.length;    
};

3.求表元素(按索引查找)

求表元素其实就是根据给定的索引值找到并返回索引对应的元素。不过在设计算法时,要考虑到健壮性,所以要把给定的是错误索引的情况也考虑进去。

// 对于顺序表的查找,除了直接返回,还可以用计数器的方式挨个遍历,找到x对应的值
int Get(SeqList L,int x){

    if(x<1 || x > length){    // 正常找第几个数都是从1开始查的,但是计算机是从0开始查的,所以此处 判断要从 1开始进行
        throw "没有该索引!";
    };
       
    return L.data[x-1];    // 计算机从0开始查找
};

4.  定位(按值查找,返回索引)

// 定位操作与查表元素类似,只不过要注意返回的索引 +1 处理
int Location(SeqList L,int x){

    int i = 0;    // 计数用
    while(i<L.length && L.data[i]!=x){    //循环查找表中值是否等于 x
        i++;
    };
    
    if(i<L.length){        // 跳出循环,说明查到了这个值
        return i+1;        // 计算机从0开始,人从1开始,所以进行 +1 处理
    }else{
        return 0;          // 若没找到,则返回0;
    };
};

    5.插入

      以下图为例,如若我要在03的位置新插入一个07,那么03及之后的元素都得后移一位。

void insert(SeqList L,DataType x,int i){    //x: 插入的元素值;i:插入的索引
    if(i<1 || i>L.length) throw "Error!";   // 新判断插入的位置对不对
    for(int j=L.length;j>=i;j--){           // 因为第i个地方的元素也得向后移动一位,所以是j>=i
        L.data[j] = L.data[j-1];            // 将前一个值赋给后一个位置
    };
    L.data[i-1] = x;                        // 将空出的 i-1 位置填充 x
    L.length+=1;                            // 注意表长度要 +1
};                

6.删除

  首先找到被删元素的位置,然后将后一位的值赋给前一索引,最后表长-1

void deleteValue(SeqList L;int i){   // i:删除i位置的值
    
    if(i<1 || i>L.length) throw "Error";    
    for(int j = i;j<L.length,j++){
        L.data[i-1] = L.data[i];    // 相当于从被删位置开始,后面所有值依次向前进一位
    };
    L.length-=1;
};

顺序表C++实例代码演示

# include <iostream>
using namespace std;

int maxSize = 100;
class SeqList{

    private:
        int length;
        int data[maxSize];
    public:
        SeqList();            // 无参构造
        SeqList(int,int);    // 有参构造
        ~SeqList();        // 析构
        int Get(int);    // 按位查找,返回值
        int Location(int)    // 定位,返回索引
        void insert(int,int);    // 插入
        void deleteValue(int);    // 删除
};

SeqList::SeqList(){
    cout<<"no param"<<endl;
};

SeqList::SeqList(int x,int data1[]){    // 有参构造

    if(x>maxSize) cout<<"越界"<<endl;
    length = x;
    for(int i=0;i<x;i++){
        data[i] = data1[i];
    };
};

SeqList::~SeqList(){        // 析构
     cout<<"SeqList end!"<<endl;  
};
int SeqList::Get(int x){        // 直接返回x-1索引的值
    if(x<1 || x>length) throw "Error";
    return data[x-1];
};
int SeqList::Location(int value){    // 按值查找
    
    for(int i=0;i<length;i++){
        if(value == data[i]){
            return i+1;        // 返回value对应的索引
        }else{
            return 0;
        };
    }; 
};

void SeqList::insert(int x,int value){
    if(x<1 || x>length) throw "Error";
    for(int i=length;i>=x;i--){            // 与伪代码逻辑一致
        data[i] = data[i-1];
    };
    data[x-1] = value;
    length++;
};

void SeqList::deleteValue(int x){
    if(x<1 || x>length) throw "Error";
    for(int i=x;i<length;i++){            // 因为是被删位置的后面所有值依次前移一位,所以索引从i-1开始
         data[i-1] = data[i];
    };
    length--;
};

顺序表Python实现

# --**coding:utf-8**--

class SeqList:

    def __init__(self,list1):
        self.list1 = list1


    def Length(self):
        # 直接调用len()函数返回长度
        return len(self.list1)

    def Get(self,n):
        # 按索引查找,先判断索引是否合法
        if(n<1 or n>len(self.list1)):
            raise Exception
        # 执行这步,说明索引合法
        return self.list1[n-1]

    def Location(self,value):
        # 按值查找,需要循环遍历操作
        for i in range(len(self.list1)):
            if self.list1[i] == value:
                return i+1              # 实际返回的值是索引+1
            else:
                return 0

    def insert(self,n,value):
        # 插入-首先找到插入点,即 n-1 及其之后的所有值都得进行后移一位,以腾出n-1这个位置放入新值
        if (n < 1 or n > len(self.list1)):
            raise Exception
        self.insert(n-1,value)

    def deleteValue(self,n):
        # 删除指定索引对应的元素
        if (n < 1 or n > len(self.list1)):
            raise Exception
        # 直接根据索引值删除
        self.list1.pop(n)
        


线性表-链表

链表的具体表示

  用一组任意的存储单元来存放数据,链表中的逻辑结构和物理结构不一定xiangt(可能相同)

类C实现单链表结构伪代码(重点放在插入和删除操作)

1.定义结构体

typedef struct node{
    TypeData data;    //数据域
    node *next;       //指针域,存储后驱结点的地址信息
}Node,*LinkList

1.初始化

LinkList InitiateLinkList(){
    LinkList head;        // 定义头结点
    head = malloc(sizeof(Node));    // 申请存储空间
    head.next = NULL;       // 指针域指向NULL
    return head;
    
}

2.求表长

int Length(LinkList head){
    int count = 0;        // 定义计数器
    int *p = head;
    while(p->next!=NULL){
        count++;          // 只要next节点不为空,计数器自增,最后计数器大小即为链表长度
        p = p->next; 
    };

};

       3.按索引查找

int Get(LinkList head,int key){
    LinkList *p;
    p = head;
    int count = 1;
    while(p->next!=NULL && count<key){
        p = p->next;
        count++;
    };
    if(count==key) return p;
    else throw "Error";
};

4.按值查找

int Location(LinkList head,int value){
    Node *p = head;
    int count = 0;
    while(p->next!=NULL && p->data!=value){    // p->next不为空且数据域和value不相等,就继续寻找下一个结点
        p = p->next;
        count++;
    };
    // 跳出循环,意味着要么找到数据,要么遍历到了最后一个结点
    if(p!=NULL) return count+1;    // 返回count+1
    else throw "Error";
}

5.插入

如下图所示,插入的核心是将新结点先搭到原链表上(先断开原来链接,搭的顺序先搭后继,再搭前驱)

void insert(LinkList head,DataType value,int key){    // value:新增结点的数据域值;key:插入的位置
     Node *p,*q;
    if(key==1) q = head;//插入的位置是1,则插入节点将替代头结点
    else q = Get(LinkList head,key-1);    // 利用Get()找到插入的位置,此时q可理解为插入节点的直接前驱
    if(q == NULL) throw "未找到插入位置"
    else{
        p = malloc(sizeof(Node));    // 新建结点
        p->data = value;            // 新节点值为value
        p->next = q->next;           //新结点的后继结点是其前驱的原后继结点
        q->next = p;                // 将直接前驱的next指向新增结点,构成新的链表
    };
};

6.删除

 删除相对于就会比较简单,因为只需要将被删结点的前驱结点的next指针指向被删结点的后继结点即可。

void deleteValue(LinkList head,int key){    // key:被删结点的索引
    // 同样要先找到被删结点的位置
    Node *q;
    if(key ==1) q = head;
    else q = GetLinkList(head,key-1);
    if(q!=NULL && q.next!=NULL){    // 保证直接前驱和后驱都存在
        Node *p = malloc(sizeof(Node));
        p = q->next;            //记录被删结点p的位置
        q->next = p->next;       // 将被删结点的前驱指向其后继
        free(p);                  // 新链表构成后,释放被删结点的内存空间
    }else throw "未找到结点位置";
};

链表C++示例代码

# include <iostream>
using namespace std;
// 构造节点结构
// struct Node{
//     int data;
//     Node *next;
// };

class Node{
    public:
        int data;
        Node *next; 
};
class LinkList{
    public:
        LinkList();     
        LinkList(int[],int);    // int[]:传入的array;int:传入的array长度
        ~LinkList();
        // 计算表长
        int Length();
        // 查找指定值
        int Location(int);
        // 指定索引查找
        Node* Get(int);
        // 插入新节点
        void insert(int,int);
        // 删除指定节点
        void deleteValue(int);
        void printLinkList();
    private:
        Node *head;
};
LinkList::LinkList(){
    head = new Node;
    head->next = NULL;
};
// int a[10] = {10,9,8,7};
LinkList::LinkList(int data[],int n){   
    // 有参构造-将传入的数组转换成链表
    head = new Node;
    head->data = 0;
    head->next = NULL;
    for (int i = 0; i < n; i++)
    {
        Node *s = new Node;
        s->data = data[i];
        s->next = head->next;
        head->next = s;
    };
    
    // while (head->next!=NULL)
    // {
    //     cout<<"p-value: "<<head->data<<endl;
    //     head = head->next;
    // };
    
};
LinkList::~LinkList(){
    cout<<"LinkList end;"<<endl;
};
int LinkList::Length(){   
    Node *p = new Node;
    p = head;   // 创建新节点p,令其为首节点
    int count = 0;          // 创建计数器,计数器结果对应链表长度
    while (p->next!=NULL)      
    {
        count++;
        p = p->next;    // 取p的下一个结点
    };
    return count;
};
Node* LinkList::Get(int n){   //按位查找,返回value
    Node *p = new Node;
    p = head;
    int count = 1;      // count=1而非0,是因为n的记录规则是从1开始。这样统一规则,避免不必要的麻烦;
    while(p->next!=NULL && count<n){    // 通过计数器和n的比较找到目标索引
        p = p->next;
        count++;
    };
    // 跳出循环,说明count起码是等于n的,直接返回p->data;
    cout<<"Get: "<<p->data<<endl;
    if (count==n)return p;
    else return NULL;

};
int LinkList::Location(int value){  // 按值查找
    Node *p = new Node;
    p = head;
    int count = 0;
    while (p->next!=NULL && p->data!=value)
    {
        count++;
        p = p->next;
    };
    if (p==NULL)
    {
        cout<< "no value" <<endl;
    }else
    {   cout<<"Location: "<<count+1<<endl;
        return count+1;
    };
    
};
void LinkList::printLinkList(){
    Node *p = new Node;
    p = head;
    while (p->next!=NULL)
    {   
        cout<<"value: "<<p->data<<endl;
        p = p->next;
    };
};
void LinkList::insert(int value,int n){
    Node *p = new Node;
    Node *q = new Node;
    if (n==1) q = head;     // 若n为1,直接令新节点是头结点
    else q = Get(n-1);      // 查找插入节点的前驱
    if (q==NULL) throw "Error";
    else
    {
        p->data=value;
        p->next = q->next;
        q->next = p;
    }; 
};
void LinkList::deleteValue(int n){
    Node *q;
    if (n==1) q = head; 
    else q = Get(n-1);  
    if (q!=NULL && q->next!=NULL)   // 确保前驱和后驱都存在
    {
       Node *p = new Node;
       p = q->next;         // 记录当前要删除的节点
       q->next = p->next;   // 将当前要删除的节点的前驱指向其后驱
       delete(p);
    }else  throw "Error";    // 插入节点不存在  
    
};

int main(){
    int a[10] = {11,9,8,7};
    LinkList s(a,4);
    s.printLinkList();
    s.Get(0);
    s.Get(1);
    s.Get(2);
    s.Get(3);
    s.insert(100,5);
    s.Get(5);
    s.Location(100);
    s.printLinkList();
    s.deleteValue(1);
    cout<<"-----------"<<endl;
    s.printLinkList();
    //LinkList q;
    // q.insert(11111,2);
    // q.printLinkList();
    // q.Location(11111);
    
    // s.Get(10);
    //q.Location(11111);
    //q.printLinkList();
    cout<<"-----------"<<endl;
    return 0;
};

说点题外话

从上述实现可以发现,顺序表在查找方面很快(O(1)),但是新增和删除很慢(O(n)),因为要大量移动结点。

而链表则相反,新增和删除很快,但是查找又没那么方便。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
线性表是一种常见的数据结构,它表示具有相同数据类型的一组元素的有序序列。线性表中的元素之间存在一种顺序关系,每个元素都有一个前驱和一个后继(除了第一个元素没有前驱,最后一个元素没有后继)。线性表可以用顺序存储结构或链式存储结构实现。 在顺序存储结构中,线性表的元素按照顺序存储在连续的内存空间中,可以通过元素的下标来访问和操作元素。插入或删除元素时,需要移动其他元素,因此操作的时间复杂度较高。 链式存储结构中,线性表的每个元素都包含一个数据域和一个指针域,指针指向下一个元素。通过指针的链接,元素可以按照任意顺序存储在内存中,插入和删除操作只需要改变指针的指向,因此时间复杂度较低。 线性表常见的操作包括插入、删除、查找、获取长度等。其中插入和删除操作需要注意保持线性表的顺序关系。 常见的线性表有数组、链表、栈和队列。数组是最简单的线性表,通过下标可以直接访问元素;链表是动态存储结构,插入和删除操作方便,但访问元素需要遍历链表;栈是一种特殊的线性表,只允许在表的一端进行插入和删除操作;队列也是一种特殊的线性表,只允许在表的一端进行插入操作,在另一端进行删除操作。这些数据结构在实际应用中都有各自的应用场景和优缺点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值