常用数据结构
目录
1.线性表
定义:
- 由n个具有相同性质的数据元素组成的有穷序列。
特点:
(一对一的关系)
- 存在唯一一个称为”第一个“的数据元素,它没有直接的前驱。
- 存在唯一一个称为“最后一个”的元素,他没有直接后驱。
- 除第一个数据元素以外,表中的每个数据元素有且仅有一个直接前驱。
- 除第一个数据元素以外,表中的每个数据元素有且仅有一个直接后驱。
1.1顺序表
定义:
- 用一组地址连续的存储单元依次存储线性表中每个数据元素。
特点:
- 逻辑关系相邻的两个元素在物理位置上也相邻。
实现:
- c语言实现:
typedef struct { int items[LISTSIZE]; int length; } SqList;
- length:线性表当前的长度。
- LISTSIZE:最大容量。
- c++实现:
template <typename T> class sqlist{ public: sqlist(int maxsize=10):Maxsize(maxsize) { elements=new T[maxsize]; length=0; } private: int Maxsize;//最大容量 T *elements;//元素初始地址 int length;//当前有效长度 };
基本操作:
初始化顺序表
//初始化顺序表,表的长度设置为0 void InitList(SqList *L) { L->length=0; }
求顺序表中当前元素的个数
//顺序表的长度 int ListLength(SqList * L) { return L->length; }
判断顺序表是否为空
//判断顺序表是否为空 bool ListEmpty(SqList *L) { if(L->length==0) return true; else return false; }
向顺序表中插入数据元素
//向顺序表中插入数据元素 bool ListInsert(SqList *L,int pos,int item) { int i; if(L->length==LISTSIZE) { printf("顺序列表已满,无法进行插入操作"); return false; } if(pos<1||pos>L->length+1) { printf("插入位置不合法"); return false; } for(i=L->length-1;i>=pos-1;i--) { L->items[i+1]=L->items[i]; } L->items[pos-1]=item; L->length++; return true; }
删除顺序表中的元素
//删除顺序表中的元素 bool ListDelete(SqList *L,int pos,int *item) { int i; if(L->length==0) { printf("顺序表为空表"); return false; } if(pos<1||pos>L->length) { printf("删除位置无效"); } *item=L->items[pos-1]; for(int i=pos-1;i<L->length-1;i++) { L->items[i]=L->items[i+1]; } L->length--; return true; }
查找指定元素在顺序表中的位置
//查找制定元素在顺序表中的位置 int Find(SqList L,int item) { int pos=0; if(ListEmpty(&L)) { printf("顺序表为空表,无法进行查找操作"); return 0; } while(pos<L.length&&L.items[pos]!=item) { pos++; } if(pos<L.length) return pos+1; else return 0; }
获取顺序表中指定位置上的数据元素
//获得顺序表中指定位置上的数据元素 int GetElem(SqList *L,int pos,int *item) { if(ListEmpty(L)) return 0; if(pos<1||pos>L->length) return 0; *item=L->items[pos-1]; return 1; }
- 遍历顺序表
//遍历顺序表 void TravereList(SqList *L) { int pos =0; while(pos<L->length) { printf("item1: %d",L->items[pos]); pos++; } }
c语言版本:
#include <iostream> using namespace std; #define LISTSIZE 100 typedef struct { int items[LISTSIZE]; int length; } SqList; //初始化顺序表,表的长度设置为0 void InitList(SqList *L) { L->length=0; } //顺序表的长度 int ListLength(SqList * L) { return L->length; } //判断顺序表是否为空 bool ListEmpty(SqList *L) { if(L->length==0) return true; else return false; } //向顺序表中插入数据元素 bool ListInsert(SqList *L,int pos,int item) { int i; if(L->length==LISTSIZE) { printf("顺序列表已满,无法进行插入操作"); return false; } if(pos<1||pos>L->length+1) { printf("插入位置不合法"); return false; } for(i=L->length-1;i>=pos-1;i--) { L->items[i+1]=L->items[i]; } L->items[pos-1]=item; L->length++; return true; } //删除顺序表中的元素 bool ListDelete(SqList *L,int pos,int *item) { int i; if(L->length==0) { printf("顺序表为空表"); return false; } if(pos<1||pos>L->length) { printf("删除位置无效"); } *item=L->items[pos-1]; for(int i=pos-1;i<L->length-1;i++) { L->items[i]=L->items[i+1]; } L->length--; return true; } //查找制定元素在顺序表中的位置 int Find(SqList L,int item) { int pos=0; if(ListEmpty(&L)) { printf("顺序表为空表,无法进行查找操作"); return 0; } while(pos<L.length&&L.items[pos]!=item) { pos++; } if(pos<L.length) return pos+1; else return 0; } //获得顺序表中指定位置上的数据元素 int GetElem(SqList *L,int pos,int *item) { if(ListEmpty(L)) return 0; if(pos<1||pos>L->length) return 0; *item=L->items[pos-1]; return 1; } //遍历顺序表 void TravereList(SqList *L) { int pos =0; while(pos<L->length) { printf("item1: %d",L->items[pos]); pos++; } } int main() { SqList Fibonacci; cout<<"建立顺序表"<<endl; InitList(&Fibonacci); for(int i=0;i<7;) { int num; cin>>num; if(ListInsert(&Fibonacci,i+1,num)) i++; } TravereList(&Fibonacci); int item; ListDelete(&Fibonacci,7,&item); TravereList(&Fibonacci); return 0; }
c++版本:
#include<iostream> using namespace std; template <typename T> class sqlist{ public: sqlist(int maxsize=10):Maxsize(maxsize) { elements=new T[maxsize]; length=0; } //打印顺序表的长度 int listLength() { return length; } //判断顺序表是否为空 bool IsEmpty() { return length==0; } //顺序表中插入元素 void InsertElement(T elem,int position) { if(IsEmpty()) { elements[length++]=elem; } else { for(int i=length;i>=position;i--) elements[i]=elements[i-1]; elements[position-1]=elem; length++; } } //顺序表中删除元素 void DelElement(T elem,int position) { T del_elem=elements[position-1]; for(int i=position-1;i<length-1;i++) elements[i]=elements[i+1]; length--; } //查找元素的位置 int FindElement(T elem) { int i; for(i=0;i<length;i++) { if(elements[i]==elem) { std::cout<<"i: "<<i<<std::endl; break; } } if(i==length) { std::cout<<"find not"<<std::endl; return -1; } else { std::cout<<"find yes"<<std::endl; return i; } } //打印 void Printf() { for(int i=0;i<length;i++) std::cout<<elements[i]<<" "; std::cout<<std::endl; } private: int Maxsize;//最大容量 T *elements;//元素初始地址 int length;//当前有效长度 }; int main() { sqlist<int> sq(20); sq.InsertElement(10,2); sq.Printf(); sq.InsertElement(30,1); sq.Printf(); sq.InsertElement(50,1); sq.Printf(); sq.InsertElement(100,2); sq.Printf(); sq.DelElement(30,3); sq.Printf(); sq.FindElement(100); return 0; }
1.2链表
定义:
- 用一组任意的存储单元存储线性表中的数据元素。
特点:
- 相比顺序存储结构,链式存储结构中,除了需要存储数据元素信息之外,还需要存储它的后继元素的存储地址(指针)。
- 单链表结点的物理位置不一定连续,单链表逻辑上通过指针实现连续。
- 单链表有一个头结点和一个尾结点,并且只有尾结点没有后继结点,其他结点有且仅有一个后继结点。
- 只要知道了链表的头结点,就可以遍历整个链表。
- 单链表的每一个结点包含两部分:数据域和指针域。
- 表头:用来存放第一个结点的地址。
- 表尾:表尾没有后继,所以尾结点的指针域为空指针(NULL)。
实现:
- c语言定义:
typedef struct node{ int data;//数据域 node * next;//指针域 }LNode,*LinkList;//LinkList是指向LNode类型数据的指针类型定义
- LinkList是指向LNode类型数据的指针类型定义 ,也就是说,在定义一个指向LNode类型数据的指针变量时,语句
LNode *L;
和
LinkList L;
是等价的。
- c++定义:
class ListNode{
public:
ListNode * next;
int data;
};
class LinkList
{
public:
LinkList()
{
node=new ListNode;
node->next=NULL;
}
~LinkList()
{
delete node;
}
void CreateLinkListBack(int n);
void CreateLinkListHead(int n);
void InsertNode(int position,int elem);
void removeNode(ListNode* p);
ListNode* findNode(int value);
void PrintLinkList();
public:
ListNode *node;
};
基本操作:
- 初始化链表
//初始化链表
LinkList init_list()
{
LinkList L=new LNode;
if(!L)
return NULL;
L->next=NULL;//指针域置空
return L;
}
- 打印链表
//打印链表
void Printf_list(LinkList &L)
{
LinkList temp=L->next;
while(temp!=NULL)
{
cout<<temp->data<<"->";
temp=temp->next;
}
}
- 插入数据(头插)
步骤:
- 先创建一个新节点s(LinkList),并给数据域赋值,
- 新节点的指针域指向L-next,
- L的指针域next指向s。
//插入数据(头插)
void create_LinkList(LinkList &L,int n)
{
while(n--)
{
LinkList s=new LNode;
cin>>s->data;
s->next=L->next;
L->next=s;
}
}
注意:使用头插法插入数据后,链表中数据的顺序和插入前的数据顺序相反。
- 插入数据(尾插)
步骤:
- 先让r节点指向尾结点,
- 创建一个新节点s(LinkList),并给数据域赋值,且s->next为NULL,因为s为尾结点,
- r的指针域指向s,
- 更新尾节点,即r=s。
//插入数据(尾插)
void create_LinkList_back(LinkList &L,int n)
{
LinkList r=L;
while(n--)
{
LinkList s=new LNode;
cin>>s->data;
s->next=NULL;
r->next=s;
r=s;
}
}
注意:使用尾插法插入数据后,链表中数据的顺序和插入前的数据顺序相同。
- 在指定位置插入数据
//插入数据
void InsertList(LinkList q,int e)
{
LinkList p=new LNode;
p->data=e;
p->next=q->next;
q->next=p;
}
- 查找相应数据
LinkList Get_elem(LinkList &L,int e)
{
LinkList temp=L->next;
while(temp!=NULL)
{
if(e==temp->data)
{
return temp;
}
temp=temp->next;
}
return NULL;
}
完整程序
#include<iostream>
using namespace std;
typedef struct node{
int data;//数据域
struct node * next;//指针域
}LNode,*LinkList;//LinkList是指向LNode类型数据的指针类型定义
//初始化链表
LinkList init_list()//引用
{
LinkList L=new LNode;
if(!L)
return NULL;
L->next=NULL;//指针域置空
return L;
}
//插入数据(头插)
void create_LinkList(LinkList &L,int n)
{
while(n--)
{
LinkList s=new LNode;
cin>>s->data;
s->next=L->next;
L->next=s;
}
}
//插入数据(尾插)
void create_LinkList_back(LinkList &L,int n)
{
LinkList r=L;
while(n--)
{
LinkList s=new LNode;
cin>>s->data;
s->next=NULL;
r->next=s;
r=s;
}
}
//打印链表
void Printf_list(LinkList &L)
{
LinkList temp=L->next;
while(temp!=NULL)
{
cout<<temp->data<<"->";
temp=temp->next;
}
cout<<endl;
}
//插入数据
void InsertList(LinkList q,int e)
{
LinkList p=new LNode;
p->data=e;
p->next=q->next;
q->next=p;
}
LinkList Get_elem(LinkList &L,int e)
{
LinkList temp=L->next;
while(temp!=NULL)
{
if(e==temp->data)
{
return temp;
}
temp=temp->next;
}
return NULL;
}
int main()
{
LinkList L=init_list();
if(!L)
{
cout<<"初始化失败"<<endl;
return 0;
}
cout<<"初始化成功"<<endl;
int num;
cout<<"请输入插入的数量:"<<endl;
cin>>num;
create_LinkList_back(L,num);
Printf_list(L);
int elem;
LinkList elem_list;
cout<<"请输入需要查找的数"<<endl;
cin>>elem;
elem_list=Get_elem(L,elem);
while(!elem_list)
{
cout<<"没有查找到,请重新输入"<<endl;
cin>>elem;
elem_list=Get_elem(L,elem);
}
cout<<"找到相应数据,数据地址为:"<<elem_list<<endl;
if(elem_list!=NULL)
{
cout<<"请输入需要插入的数"<<endl;
int elem_insert;
cin>>elem_insert;
InsertList(elem_list,elem_insert);
Printf_list(L);
}
return 0;
}
c++版本:
#include <iostream>
using namespace std;
class ListNode{
public:
ListNode * next;
int data;
};
class LinkList
{
public:
LinkList()
{
node=new ListNode;
node->next=NULL;
}
~LinkList()
{
delete node;
}
void CreateLinkListBack(int n);
void CreateLinkListHead(int n);
void InsertNode(int position,int elem);
void removeNode(ListNode* p);
ListNode* findNode(int value);
void PrintLinkList();
public:
ListNode *node;
};
//头插
void LinkList::CreateLinkListHead(int n)
{
while(n--)
{
//插入的数据
ListNode* l=new ListNode;
cin>>l->data;
l->next=node->next;
node->next=l;
}
}
//尾插
void LinkList::CreateLinkListBack(int n)
{
ListNode* r=node;
while(n--)
{
ListNode* l=new ListNode;
cin>>l->data;
l->next=NULL;
r->next=l;
r=l;
}
}
//在第i个位置插入元素
void LinkList::InsertNode(int position,int elem)
{
int j;
ListNode* p=node->next;
for(j=1;j<position-1;j++)
{
p=p->next;
if(p==NULL)
break;
}
if(p==NULL &&j<(position-1))
return ;
// while(p)
// {
// j++;
// if(j==position)
// break;
// p=p->next;
// }
ListNode* q=new ListNode;
q->data=elem;
q->next=p->next;
p->next=q;
}
//移除节点
void LinkList::removeNode(ListNode* p)
{
if(p==NULL)
return;
ListNode* temp=node;
while(temp->next!=p)
{
temp=temp->next;
}
temp->next=p->next;
delete p;
}
//寻找某节点
ListNode* LinkList::findNode(int value)
{
ListNode* temp=node->next;
while(temp)
{
if(temp->data==value)
return temp;
temp=temp->next;
}
return NULL;
}
//打印链表
void LinkList::PrintLinkList()
{
ListNode* temp=node->next;
while(temp)
{
cout<<temp->data<<"->";
temp=temp->next;
}
std::cout<<std::endl;
}
int main()
{
LinkList list;
list.CreateLinkListHead(5);
list.PrintLinkList();
list.InsertNode(4,9);
list.PrintLinkList();
ListNode* address=list.findNode(2);
list.removeNode(address);
list.PrintLinkList();
return 0;
}
2.栈
-
定义:
栈(stack)是一种元素满足后进先出(Last in first out, LIFO)规则的线性表。
-
特点:
- 栈的本质是一种线性表, 但是具有其特殊的操作要求——元素后进先出。这种要求也决定了栈的操作是在表尾进行
- 栈底(bottom):栈的表头
- 栈顶(top):将栈的表尾,所以栈的所有操作都是在栈顶进行的。
- 添加元素,称为入栈(push);删除元素,称为出栈(pop)。
-
实现:
栈可以用顺序存储实现,也可以用链式存储实现,分别成为顺序栈和链栈。但一般使用顺序存储实现,因为栈不允许在栈中间进行插入和删除,需要准寻栈特有的规则。
- 动态实现
typedef struct SqStack_dynamic{
int* base;//栈底
int* top;//栈顶
}SqStack_dynamic;
- 静态实现
typedef struct SqStack_static{
int data[Maxsize];
int top;//栈顶
}SqStack_static;
- C++
class stack_static{
public:
stack_static()
{
MaxSize=10;
top=0;
data= new int[MaxSize];
}
void push(int e);
void pop(int &e);
void GetTop(int &e);
private:
int MaxSize;
int top;
int *data;
};
操作:
- 初始化
void InitStack(SqStack_dynamic &stack)
{
stack.base= new int[Maxsize];
stack.top=stack.base;
}
- 入栈
void push(SqStack_dynamic &stack,int e)
{
if((stack.top-stack.base)>=Maxsize)
return;
*stack.top=e;
stack.top++;
}
void stack_static::push(int e)
{
data[top]=e;
top++;
}
- 出栈
void pop(SqStack_dynamic &stack,int &e)
{
if(stack.base==stack.top)
return;
stack.top--;
e=*stack.top;
}
void stack_static::pop(int &e)
{
top--;
e=data[top];
}
- 栈顶元素
int GetTop(SqStack_dynamic &stack)
{
if(stack.base!=stack.top)
return *(stack.top-1);
else
return -1;
}
void stack_static::GetTop(int &e)
{
if(top!=MaxSize)
e=data[top-1];
else
e=-1;
}
完整程序
#include<iostream>
using namespace std;
#define Maxsize 100
typedef struct SqStack_dynamic{
int* base;//栈底
int* top;//栈顶
}SqStack_dynamic;
class stack_static{
public:
stack_static()
{
MaxSize=10;
top=0;
data= new int[MaxSize];
}
void push(int e);
void pop(int &e);
void GetTop(int &e);
private:
int MaxSize;
int top;
int *data;
};
void stack_static::push(int e)
{
data[top]=e;
top++;
}
void stack_static::pop(int &e)
{
top--;
e=data[top];
}
void stack_static::GetTop(int &e)
{
if(top!=MaxSize)
e=data[top-1];
else
e=-1;
}
void InitStack(SqStack_dynamic &stack)
{
stack.base= new int[Maxsize];
stack.top=stack.base;
}
void push(SqStack_dynamic &stack,int e)
{
if((stack.top-stack.base)>=Maxsize)
return;
*stack.top=e;
stack.top++;
}
void pop(SqStack_dynamic &stack,int &e)
{
if(stack.base==stack.top)
return;
stack.top--;
e=*stack.top;
}
int GetTop(SqStack_dynamic &stack)
{
if(stack.base!=stack.top)
return *(stack.top-1);
else
return -1;
}
int main()
{
SqStack_dynamic stack;
InitStack(stack);
push(stack,2);
push(stack,5);
push(stack,10);
std::cout<<"------dynamic stack-----"<<std::endl;
int num;
pop(stack,num);
std::cout<<num<<" ";
pop(stack,num);
std::cout<<num<<" ";
pop(stack,num);
std::cout<<num<<" ";
stack_static stack_s;
stack_s.push(5);
stack_s.push(10);
stack_s.push(20);
std::cout<<std::endl;
std::cout<<"------static stack-----"<<std::endl;
int num_s;
stack_s.pop(num_s);
std::cout<<num_s<<" ";
stack_s.pop(num_s);
std::cout<<num_s<<" ";
stack_s.pop(num_s);
std::cout<<num_s<<" ";
return 0;
}
3.树
1.定义
树是指由 n(n≥0)个结点组成的有穷集合 D 与 D 上的关系的集合 R构成的结构,通常我们用 T 表示。树需要满足以下 3 个条件:
- 当 n=0 时,该结点集合为空,该树被称为空树。
- 在任意的非空树中,有且仅有一个根结点 root。
- 当 n>1 时 ,除根结点以外的其余结点可以分为 m(m>0)个不相交的子集D1,D2,D3,…,Dm, 其中每一个这样的子集 Di(i≤m)本身又是一棵树, 称为根结点 root的子树
注意:
- 树的定义是一个递归定义,因为树是由根结点和子树构成的,而每一个子树也是一棵树。
- 从图中我们还了解,树结构在形式上最主要的特点就是分层特点和具有一定的排列顺序。
2.树的表示方法
- 树形表示法
- 文氏图表示法
3.基本术语
-
结点的度
结点拥有的直接子树数目。如图 A结点的度为3,B结点的度为2,c结点的度为1,D结点的度为3,E、F、G、H、I 以及J度都为0
-
树的度
树中各结点度的最大值称为该树的度。如上图,树的度为3.
-
分支结点
度不为 0 的结点称为分支结点或普通结点。
- 叶子结点
度为 0 的结点称为叶子结点或终端结点。由此可知叶子结点没有子结点。
-
结点的层次
从树的根结点开始,根结点为第 1 层,根结点的子结点为第 2 层,第 2 层结点的子结点为第 3 层……
-
树的深度
树中结点的最大层次数被定义为该树的深度或高度。
-
森林
n(n≥0)棵不相交的树的集合称为森林。
4.基本结构
- 二叉树具有的几种形态
5.性质
6. 存储方式
- 顺序存储
顺序存储将二叉树的每一个节点和数组的编号一一对应,根据这个编号,可以找到该结点的父结点和孩子结点
BinaryTree[0]表示该树的根结点,从根结点开始我们可以按照性质 6 去检索该树的所有结点,比如一个有 20 个结点的
二叉树, 根据性质 6 编号为 10 的结点的父结点为编号为 5 的结点,其左孩子结点为编号为20 的结点,其没有右孩子结点。由此可以看出:
- 如果二叉树是一棵完全二叉树的话,顺序结构的优点就非常明显,它能紧凑的存储二叉树,而且可以随机存取二叉树的某个结点,只需要根据性质 6 计算出该结点的元素下标即可;
- 如果是非完全二叉树,则顺序存储结构就存在空间的浪费,它会为不存在的结点分配空间,而且如果二叉树规模很大,在实际应用中,内存里也很难找出相应的连续存储空间。
- 链式存储
二叉树的节点一般定义为:数据部分、左子树指针和右子树指针,用指针将节点串起来,其中 l 表示左孩子指针, r 表示右孩子指针,∧表示空指针
优点:
- 能够很直观地表示二叉树的逻辑结构;
- 在二叉树不是完全二叉树的时候,链式结构不会像顺序存储结构一样产生空结点,而且链式存储结构能很好地利用内存中的碎片空间,提高内存空间利用效率。
缺点:
- 链式存储结构不能随机存取某个结点,要找到某个结点,必须从根结点开始沿着指针往叶子结点遍历。
7.基本操作
参考:
- 构建一棵树,即初始化操作
#include<iostream>
using namespace std;
class BinaryTreeNode{
public:
//init
BinaryTreeNode(){
data=NULL;
lchild=NULL;
rchild=NULL;
}
BinaryTreeNode(int n){
data=new int;
*data=n;
lchild=NULL;
rchild=NULL;
}
private:
int *data;//data
BinaryTreeNode* lchild;//left child tree
BinaryTreeNode* rchild;//right child tree
};
int main()
{
BinaryTreeNode bt(5);
return 0;
}
- 前序遍历(根、左、右)
//递归
void preorderTraversal(BinaryTreeNode *node)
{
if(!node)
return;
std::cout<<node->data<<" ";
preorderTraversal(node->lchild);
preorderTraversal(node->rchild);
}
//迭代
void preoder(BinaryTreeNode *root)
{
BinaryTreeNode *cur=root;
std::stack<BinaryTreeNode *>s;
while(cur||!s.empty())
{
//遍历直到最左叶子节点
while(cur)
{
std::cout<<cur->data<<" ";
s.push(cur);
cur=cur->lchild;
}
//while结束时,cur=NULL
//cur为左子节点
cur=s.top();
s.pop();
cur=cur->rchild;
}
}
- 中序遍历(左、根、右)
void midorderTraversal(BinaryTreeNode *node)
{
if(!node)
return;
preorderTraversal(node->lchild);
std::cout<<node->data<<" ";
preorderTraversal(node->rchild);
}
void midoder(BinaryTreeNode *root)
{
BinaryTreeNode *cur=root;
std::stack<BinaryTreeNode *>s;
while(cur||!s.empty())
{
//遍历直到最左叶子节点
while(cur)
{
s.push(cur);
cur=cur->lchild;
}
//while结束时,cur=NULL
//cur为左子节点
cur=s.top();
std::cout<<cur->data<<" ";
s.pop();
cur=cur->rchild;
}
}
- 后序遍历(左、右、根)