时间复杂度与空间复杂度:
时间复杂度即运行时间的需求,空间复杂度即对空间的需求
线性表:
线性表记为(a1,a2,,,,ai-1,ai,ai+1,,,,an),则ai-1为ai的直接前驱元素,ai+1为ai的直接后继元素,存在多个元素的前提下,第一个元素无前驱,最后一个元素无后继,其他元素都有且只有一个前驱和一个后继。
数据类型:
指一组性质相同的值的集合及定义在此集合上的一些操作的总称。总的来说,就是整型,浮点型,字符型等数据类型,
原子类型:不可再分解的类型;整型,字符型,浮点型等,
结构类型:由若干个类型组合而成,如整型数组是由若干整型数据构成的。
抽象数据类型格式:
ADT 抽象数据类型名
Data 数据元素之间的逻辑关系的定义
Operation
操作
endADT
线性表的抽象数据类型:
ADT 线性表(List)
Data 线性表中的每个元素的类型均为DataType,数据元素之间是一对一的关系。
Operation
InitList(*L):初始化操作,建立一个空的线性表L。
ListEmpty(L):判断线性表是否为空表,若线性表为空,返回true,否则返回false。
ClearList(*L):将线性表清空。
GetElem(L,i,*e):将线性表L中的第i个位置元素值返回给e。
LocateElem(L,e):在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中学号表示成功;否则,返回0表示失败。
ListInsert(*L,i,e):在线性表L中第i个位置插入新元素e。
ListDelete(*L,i,e):删除线性表L中第i个位置元素,并用e返回其值。
ListLength(L):返回线性表L的元素个数。
endADT
线性表的顺序存储结构:
指的是用一段地址连续的存储单元依次存储线性表的数据元素。
线性表的顺序存储为:
a1,a2,a3,,,,,,,ai-1,ai,ai+1,,,,,an`
线性表顺序存储的结构代码:
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE];
int length; //线性表当前长度
} SqList;
顺序存储结构封装需要三个属性:
1.存储空间的起始位置,数组data,它的存储位置就是线性表存储空间的存储位置。
2.线性表的最大存储容量:数组的长度MaxSize。
3.线性表的当前长度:length。
注:数组的长度与线性表的当前长度需要区分:数组的长度是存放线性表的存储空间的总长度,一般初始化后不变,而线性表的当前长度是线性表中元素的个数,是会变化的。
线性表的地址从1开始
优点:
1.无须为表示表中元素的逻辑关系而增加额外的存储空间。
2.可以快速地存取表中任意位置的元素
缺点:
1.插入和删除操作需要移动大量元素
2.当线性表长度变化较大时,难以确定存储空间的容量。
3.容易造成存储空间的“碎片”
线性表的链式存储结构:
是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以存在内存中未被占用的任意位置。除了要存储数据元素的信息外,还需要存储它的后继元素的存储地址(指针)。
结构定义:
我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成数据元素称为存储映像,称为结点(Node)。
若链表的每个结点中只包含一个指针域,所以叫做单链表
单链表图示:
线性表,总得有个头有个尾,链表也不例外,我们把链表中的第一个结点的存储位置叫做头指针,最后一个结点指针为空(NULL)。
空链表图示:
头指针与头结点的异同:
头指针:
1.头指针是指链。表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
2.头指针具有标识作用,所以常用头指针冠以链表的名字(指针变量的名字)。
3.无论链表是否为空,头指针均不为空。
4.头指针是链表的必要元素。
头结点:
1.头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(但也可以用来存放链表的长度)。
2.有了头结点,对在第一元素结点钱插入结点和删除第一结点起操作与其他结点的操作就统一了。
3.头结点不一定是链表的必须要素。
单链表存储结构(C语言中的结构指针描述单链表):
typedef struct Node
{
ElemType data; //数据域
struct Node* Next; //指针域
} Node;
typedef struct Node* LinlLst;
C/C++ 单链表的创建、插入、删除、查找实现(简单 - 完整代码)
https://blog.csdn.net/qq_43126471/article/details/92065858?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control
单链表的整表创建:
单链表数据可以是分散在内存的各个角落,其增长也是动态的,
对于每个链表来说,它所占用的空间的大小和位置是不需要预先分配划定的,可以根据系统的情况和实际的需求即时生成。
静态链表:
注:
1.对数组的第一个和最后一个元素特殊处理,他们的data不存放数据
2.通常把未使用的数组元素称为备用链表。
3.数组的第一个元素,即下标为0的那个元素的cur就存放在备用链表的第一个结点的下标。
4.数组的最后一个元素,即下标为MAXSIZE-1的cur则存放第一个有数值的元素的下标,相当于单链表中的头结点作用。
静态链表的优缺点总结:
静态链表是为了给没有指针的编程语言设计的一种实现单链表功能的方法。
单链表腾讯面试题:
题目:快速找到未知长度单链表的中间结点。
利用快慢指针原理:设置两个指针*search、*mid都指向单链表的头节点。其中*rearch的移动速度是*mid的2倍,当*search指向末尾结点的时候,mid刚好就在中间了,即标尺的思想
主要代码为:
search = search->next->next;
mid = mid->next;
循环链表:
终端结点用尾指针rear表示,开始结点为rear->next->next。
约瑟夫问题:
问题描述:设有n个人围坐在圆桌周围,现从某个位置m(1≤m≤n)上的人开始报数,报数到k的人就站出来。下一个人,即原来的第k+1个位置上的人,又从1开始报数,再报数到k的人站出来。依次重复下去,直到全部的人都站出来为止。试设计一个程序求出这n个人的出列顺序。
#include<iostream>
using namespace std;
typedef struct Lnode
{
int data;
struct Lnode *next;
}Lnode, *Linklist; //循环链表结点类型定义
//创建循环链表
Linklist Initlist(Linklist L, int n)
{
Linklist q;
q = L;
L->data = 1;
for (int i = 2; i <= n; i++)
{
Linklist p;
p = (Lnode*)malloc(sizeof(Lnode));
p->data = i;
p->next = NULL;
q->next = p;
q = p;
}
while (q->next == NULL)
{
q->next = L;
}
return L;
}
//删除报数为k的结点
Linklist Deletelist(Linklist L, int m, int k, int n, int w)
{
Linklist p, q;
p = q = L;
if (m == 1)
{
if (n == w);
else
{
for (int i = 0; i < k-m; i++)
{
p = p->next;
}
}
}
else
{
for (int i = m; i < m + k - 1; i++)
{
p = p->next;
}
}
q = p->next;
p->next = q->next;
q->next = NULL;
free(q);
L = p;
return L;
}
//获取报数为k的序号
int Getnumlist(Linklist L, int m, int k, int n,int w)
{
Linklist p;
p = L;
if (m == 1)
{
if (n == w)
for (int i = 0; i < k - m; i++)
{
p = p->next;
}
else
for (int i = 0; i < k - m + 1; i++)
{
p = p->next;
}
}
else
{
for (int i = m; i < m + k; i++)
{
p = p->next;
}
}
return p->data;
}
int main()
{
int n,m,k;
cout << "有__个人围坐在圆桌周围,请输入:";
cin >> n;
cout << "从某个位置__上的人开始报数,请输入:";
cin >> m;
cout << "报数到__的人就站出来,请输入:";
cin >> k;
int num, *a, i = 0,w;
w = n;
Linklist L;
L = (Lnode*)malloc(sizeof(Lnode));
L = Initlist(L, n);
a = (int*)malloc(sizeof(int)*n);
while (n != 0)
{
num = Getnumlist(L, m, k,n,w);
a[i++] = num;
L = Deletelist(L, m, k,n,w);
n--;
}
cout << "这" << w << "人的出列顺序为:";
for (int i = 0; i < w; i++)
{
cout << a[i] << " ";
}
cout << endl;
return 0;
}
判断单链表是否有环:
有环的定义是,链表的尾结点指向了链表中的某一个结点。
判断方法:
1.使用p、q两个指针,p总是向前走,但q每次都是从头开始走,对于每个结点,看p走的步数是否和q一样,若p和q的步数不等,出现矛盾,存在环
2.使用p、q两个指针,p每次向前走一步,q每次向前走两步,若在某个时候p==q,则存在环。
魔术师发牌问题:
问题描述:魔术师利用衣服牌中13张黑牌,预先将他们排好后,牌面朝下。对观众说:“我不看牌,只数数就可以猜到每张牌是什么,我大声数数,你们听。不信,现场人士,魔术师将最上面的那张牌数为1把他翻过来正好是黑桃A将黑桃A放在桌子上,第二次数1,2将第一张排放在这些牌的下面,将第二张牌翻过来,正好是黑桃2,也将它放在桌子上这样一次进行将13张牌全部放出,准确无误”
#include<iostream>
using namespace std;
struct node{ //创建结点
int data;
node* next;
node (int data)
{
this->data=data;
}
node()
{}
};
typedef node* pointer;
pointer creatList() //创建并初始化循环链表
{
pointer head,current,newnode;
head=current=new node;
head->data=0;
int N=12;
while(N>=1)
{
newnode=new node(0);
current->next=newnode;
current=current->next;
N--;
}
current->next=head;
return head;
}
void Magician(pointer List) //模拟魔术师发牌
{
pointer ptr;
ptr=List;
ptr->data=1;
int card=2;
while(1)
{
for(int i=0;i<card;i++)
{
ptr=ptr->next;
if(ptr->data!=0)
i--;
}
if(ptr->data==0)
{
ptr->data=card++;
if(card>13)
break;
}
}
}
void Display(pointer List) //打印结果
{
pointer current=List;
pointer head=current;
while(current->next!=head)
{
cout<<current->data<<' ';
current=current->next;
}
cout<<current->data;
}
int main()
{
pointer List=creatList();
Magician(List);
cout<<"原来牌的顺序为:"<<endl;
Display(List);
return 0;
}
拉丁方阵问题:
在N行N列的数阵中, 数K(1〈=K〈=N)在每行和每列中出现且仅出现一次,这样的数阵叫N阶拉丁方阵。例如下图就是一个五阶拉丁方阵。编一程序,从键盘输入N值后,打印出所有不同的N阶拉丁方阵,并统计个数。
1 2 3 4 5
2 3 4 5 1
3 4 5 1 2
4 5 1 2 3
5 1 2 3 4
#include <iostream>
#include <stdio.h>
using namespace std;
void printM(int M)
{
//只有一个数的时候输出1
if (M == 1)
{
printf("1");
}
//生成在拉丁方阵中出现的数据序列
int *elementArray = new int[M];
for (int i = 0; i < M; i++)
{
elementArray[i] = i + 1;
}
//动态生成一个二维数组
int **shouList = new int*[M];//开辟行
for (int i = 0; i < M; i++)
shouList[i] = new int[M]; //开辟列
for (int i = 0; i < M; i++)
{
//写入一行方阵
for (int k = 0; k < M; k++)
{
shouList[i][k] = elementArray[k];
}
//将数据序列循环左移一位
int temp;
temp = elementArray[0];
for (int i = 0; i < M - 1; i++)
{
elementArray[i] = elementArray[i+1];
}
elementArray[M - 1] = temp;
}
//打印方阵
for (int i = 0; i < M; i++)
{
for (int j = 0; j < M; j++)
{
printf("%2d", shouList[i][j]);
}
printf("\n");
}
};
int main()
{
int M = 0;
printf("Please input M :");
scanf_s("%d",&M);
printM(M);
system("pause");
return 0;
}
双向链表:
双向链表结点结构:
typedef struct DualNode
{
ElemType data;
struct DualNode *prior; //前驱结点
struct DualNode *next; //后继结点
} DualNode, *DuLinkList;
双向循环链表:
对于双向链表,某一结点的后继结点的前驱结点就是它本身。每个结点多了一个prior的指针
维吉尼亚加密:
当输入明文,自动生成随机密钥匹配明文中每个字母,并移位加密
// 维吉尼亚.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include<stdio.h>
#include<string.h>
#pragma warning(disable : 4996)
#define N 1000
void MString(char a[],char b[]);
void KString(char d[],char e[]);
int main() {
char a[N],b[N],d[N],e[N];
char choice; //定义的明文,密钥,明文的字符数组//
int i,j,q;
do{ //设置选择菜单//
printf(" 维吉尼亚密码\n ");
printf("*************************************************\n");
printf("1.Encrypt\n");
printf("2.Decrypt\n");
printf("0.退出\n");
printf("*************************************************\n");
printf("请输入以~2或者~2开头的字符串,0为退出");
scanf("%s",&choice);
switch(choice) {
case '1':
printf("请输入明文,密钥:\n");
getchar();
gets(a);
j=1;
for(i=0;i<(int)strlen(a);i++) {
if(('A'<=a[i]&&a[i]<='Z')||('a'<=a[i]&&a[i]<='z')||(a[i]==32))
{ j=j+2; //排除输入密文和密钥中的非法字符//
}else{
j=0;
printf("输入错误");
break;
}
}
gets(b);
for(i=0;i<(int)strlen(b);i++) {
if(('A'<=b[i]&&b[i]<='Z')||('a'<=b[i]&&b[i]<='z')||(b[i]==32)){
j=j+2;
}else{
j=0;
printf("输入错误");
break;
}
}
if(j!=1)
{
MString(a,b);
}
break;
case '2':
printf("请输入密文,密钥:\n");
getchar();
gets(d);
q=1;
for(i=0;i<(int)strlen(d);i++) {
if(('A'<=d[i]&&d[i]<='Z')||('a'<=d[i]&&d[i]<='z')||(d[i]==32))
{ q=q+2; //排除输入密文和密钥中的非法字符//
}else{
q=0;
printf("输入错误");
break;
}
}
gets(e);
for(i=0;i<(int)strlen(e);i++) {
if(('A'<=e[i]&&e[i]<='Z')||('a'<=e[i]&&e[i]<='z')||(e[i]==32))
{ q=q+2; //排除输入密文和密钥中的非法字符//
}else{
q=0;
printf("输入错误");
break;
}
}
if(q!=1)
{
KString(d,e);
}
break;
case '0':
printf("退出\n");
break;
}
}while(choice!='0'||'1'||'2');
printf("请重新输入: ");
}
//加密函数//
void MString(char a[],char b[]) {
char c[N];
char str[53]= {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','\0'}; //定义字符串数组从a~zA-Z依次排序//
int i,m,n,r;
int lena=(int)strlen(a);
int lenb=(int)strlen(b);
int i2=0;
printf("密文:");
for(i=0; i<lena; i++) {
if(a[i]==32){
c[i]=32;
}else{
if(b[i2%lenb]>='a'&&b[i2%lenb]<='z') {
n=b[i2%lenb]-97;
}
if(b[i2%lenb]>='A'&&b[i2%lenb]<='Z') {
n=b[i2%lenb]-65;
}
if(a[i]>='a'&&a[i]<='z') {
m=a[i]-97;
r=(m+n)%26;
}
if(a[i]>='A'&&a[i]<='Z') {
m=a[i]-65;
r=(m+n)%26+26;
}
i2++;
c[i]=str[r];
}
printf("%c",c[i]);
//清空密钥数组防止下一次运行用到本次密钥//
}
}
//解密函数//
void KString(char d[],char e[]) {
int i,m1,n1,r1;
char str[53]= {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','\0'}; //定义字符串数组从a~z依次排序//
char f[N];
int lend=(int)strlen(d);
int lene=(int)strlen(e);
int i2=0;
printf("明文:");
for(i=0; i<lend; i++) {
if(d[i]==32){
f[i]=32;
}else {
if(e[i2%lene]>='a'&&e[i2%lene]<='z') {
n1=e[i2%lene]-97;
}
if(e[i2%lene]>='A'&&e[i2%lene]<='Z') {
n1=e[i2%lene]-65;
}
if(d[i]>='a'&&d[i]<='z') {
m1=d[i]-97;
r1=(m1+26-n1)%26;
}
if(d[i]>='A'&&d[i]<='Z') {
m1=d[i]-65;
r1=(m1+26-n1)%26+26;
}
i2++;
f[i]=str[r1];
}
printf("%c",f[i]);
//清空密钥数组防止下一次运行用到本次密钥//
}
}