线性表
前言
本文主要介绍线性表的基本概念以及基本运算方式。
示范样例包括:类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)),因为要大量移动结点。
而链表则相反,新增和删除很快,但是查找又没那么方便。