线性表
定义:
- 由n个具有相同性质的数据元素组成的有穷序列。
特点:
(一对一的关系)
- 存在唯一一个称为”第一个“的数据元素,它没有直接的前驱。
- 存在唯一一个称为“最后一个”的元素,他没有直接后驱。
- 除第一个数据元素以外,表中的每个数据元素有且仅有一个直接前驱。
- 除第一个数据元素以外,表中的每个数据元素有且仅有一个直接后驱。
1.1 顺序表
定义:
- 用一组地址连续的存储单元依次存储线性表中每个数据元素。
特点:
- 逻辑关系相邻的两个元素在物理位置上也相邻。
- 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 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; 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++定义:
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; 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; }
栈
-
定义:
栈(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;
}
树
定义
树是指由 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;
}
}