c++软件开发面试题总结

  1. volatile关键字
    volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错。加了volatile修饰的变量,编译器将不对其相关代码执行优化,而是生成对应代码直接存取原始内存地址)。

volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
2. 一个C++源文件从文本到可执行文件经历的过程?
对于C/C++编写的程序,从源代码到可执行文件,一般经过下面四个步骤:
1).预处理,产生.ii文件
2).编译,产生汇编文件(.s文件)
3).汇编,产生目标文件(.o或.obj文件)
4).链接,产生可执行文件(.out或.exe文件)

1): 首先是源代码文件.cpp和相关的头文件预处理成一个.i文件。
预处理主要包含下面的内容:
a.对所有的“#define”进行宏展开;
b.处理所有的条件编译指令,比如“#if”,“#ifdef”,“#elif”,“#else”,“#endif”
c.处理“#include”指令,这个过程是递归的,也就是说被包含的文件可能还包含其他文件
d.删除所有的注释“//”和“/**/”
e.添加行号和文件标识
f.保留所有的“#pragma”编译器指令
经过预处理后的.ii文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到.ii文件中。

2): 编译的过程就是将预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件(.s文件)

3): 汇编是将汇编代码转变成机器可以执行的代码,每一个汇编语句几乎都对应一条机器指令。最终产生目标文件(.o或.obj文件)。

4): 链接的过程主要包括了地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位(Relocation)

静态链接: 在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中,因此对应的链接方式称为静态链接。

动态链接: 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
3. 内联函数与普通函数的区别:
内联函数和普通函数最大的区别在于内部的实现方面,而不是表面形式,我们知道普通函数在被调用时,系统首先要跳跃到该函数的入口地址,执行函数体,执行完成后,再返回到函数调用的地方,函数始终只有一个拷贝;而内联函数则不需要进行一个寻址的过程,当执行到内联函数时,此函数展开(很类似宏的使用),直接嵌入到目标代码中,如果在 N处调用了此内联函数,则此函数就会有N个代码段的拷贝。

  1. 内联函数与宏定义的区别:

  2. 宏不是函数,内联函数属于函数;

  3. 宏是在预处理时展开,内联函数在编译时展开;

  4. 宏是简单的文本替换(注意是字符串的替换,不是其他类型参数的替换),内联函数直接嵌入到目标代码中;

  5. 内联函数中有类型安全检查、语句是否正确等操作,而宏中没有;

  6. 宏在处理时,最好用括号括起来,以避免二义性,但内联函数不存在这个问题。

  7. vector的内存分配与释放:
    内存分配:C++ vector其实就相当于动态数组,它的内存会随着size的增加而不断的增长。vector有两个函数,一个是capacity(),返回对象缓冲区(vector维护的内存空间)实际申请的空间大小,另一个size(),返回当前对象缓冲区存储数据的个数。对于vector来说,capacity是永远大于等于size的,当capacity和size相等时,vector就会扩容,capacity变大。
    比如vector最常用的push_back操作,它的整个过程是怎么一个机制呢?
    在调用push_back时,若当前容量(capacity)已经不能够容纳新的元素,即此时capacity=size,那么vector会重新申请一块内存,申请的内存容量是原vector容量的2倍大小,然后把之前的内存里的元素拷贝到新的内存当中,然后把push_back的元素拷贝到新的内存中,释放旧内存。

内存释放:对于vector,我们可以用swap( )函数来释放内存。
首先创建一个临时的vector变量,利用swap( )函数来交换临时变量与原vector变量,执行完交换后,临时的vector变量会自动被销毁,内存得到释放,从而达到释放原有vector内存的目的。

  1. Linux和os: netstat tcpdump ipcs ipcrm
    netstat : netstat命令用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。netstat是在内核中访问网络及相关信息的程序,它能提供TCP连接,TCP和UDP监听,进程内存管理的相关报告。

从整体上看,netstat的输出结果可以分为两个部分:
一个是Active Internet connections,称为有源TCP连接,其中"Recv-Q"和"Send-Q"指的是接收队列和发送队列。这些数字一般都应该是0。如果不是则表示软件包正在队列中堆积。这种情况只能在非常少的情况见到。
另一个是Active UNIX domain sockets,称为有源Unix域套接口(和网络套接字一样,但是只能用于本机通信,性能可以提高一倍)。
Proto显示连接使用的协议, RefCnt表示连接到本套接口上的进程号,Types显示套接口的类型,State显示套接口当前的状态,Path表示连接到套接口的其它进程使用的路径名。
tcpdump: 对网络上的数据包进行截获的包分析工具。普通情况下,直接启动tcpdump将监视第一个网络接口(一般是eth0)上所有流过的数据包。

ipcs:提供进程间通信方式的信息,包括共享内存,信号量,消息队列。

ipcrm:移除一个消息队列、或者共享内存、或者信号量,同时会将与ipc对象相关链的数据也一起移除

  1. c++进程内存空间分布(注意各部分的内存地址谁高谁低,注意栈从高到低分配,堆从低到高分配)
    内存分布分为5个部分,从高地址到低地址依次为 栈区(stack),堆区(heap),未初始化数据段(uninitialized data),初始化数据段(initialize data),代码段(text)。

1、栈区(stack)— 由系统管理,由高地址向低地址扩展。程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。程序结束时由编译器自动释放。
2、堆—是动态内存分配通常发生的部分。动态内存,由用户管理,内存分配由低地址到高地址,分配方式类似于数据结构的链表。堆是由程序员自己分配的。(malloc和new从堆区分配内存)
3、未初始化的数据段—通常称为bss段,这个段的数据在程序开始之前有内核初始化为0,包含所有初始化为0和没有显示初始化的全局变量和静态变量,
4、初始化的数据段—通常称为数据段,包含已经初始化的全局变量和静态变量。
5、代码段—存放函数体的二进制代码和常量,通常代码段是共享的,对于经常执行的程序,只有一个副本需要存储在内存中,代码段是只读的,以防止程序以外修改指令。
8. 多线程和多进程的区别
进程就是程序在计算机上的一次执行活动。线程就是把一个进程分为很多片,每一片都可以是一个独立的流程。这已经明显不同于多进程了,进程是一个拷贝的流程,而线程只是把一条河流截成很多条小溪。它没有拷贝这些额外的开销,但是仅仅是现存的一条河流,就被多线程技术几乎无开销地转成很多条小流程,它的伟大就在于它少之又少的系统开销。
线程安全的条件:
要确保函数线程安全,主要需要考虑的是线程之间的共享变量。属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈和寄存器。因此,对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量、局部静态变量、分配于堆的变量都是共享的。在对这些共享变量进行访问时,如果要保证线程安全,则必须通过加锁的方式。
9. 动态链接和静态链接的区别
静态链接和动态链接两者最大的区别就在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时。简单地讲动态链接就是运行时的链接, 静态链接就是编译时的链接.
静态链接:是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。换句话说,函数和过程的代码就在程序的.exe文件中,该文件包含了运行时所需的全部代码。当多个程序都调用相同函数时,内存中就会存在这个函数的多个拷贝,这样就浪费了宝贵的内存资源。
动态链接:动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的符号、参数信息(往往是一些符号重定位信息)。仅当应用程序被装入内存开始运行时,在Windows的管理下,才在应用程序与相应的dll之间建立链接关系。当要执行所调用dll中的函数时,根据链接产生的重定位信息,Windows才转去执行dll中相应的函数代码。

静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。
但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,能够在其它同类操作系统的机器上直接运行,比如一个exe文件是在WIN2000系统上静态链接的,那么将该文件直接拷贝到另一台WIN2000的机器上,是可以运行的。在执行的时候运行速度快。
动态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。
但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。
10. 什么是死锁?如何避免死锁?
所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
死锁的发生必须具备以下四个必要条件:
1)互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
2)请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求时,该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
3)不可剥夺条件: 进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
4)循环等待条件: 若干进程间形成首尾相接循环等待资源的关系。即进程集合{P0,P1,P2,…,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

避免死锁的方法:
1)死锁避免的基本思想:

系统对进程发出每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。

2)如何避免死锁?

在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。 一般来说互斥条件是无法破坏的,所以在预防死锁时主要从其他三个方面入手 :

(1)破坏请求和保持条件:在系统中不允许进程在已获得某种资源的情况下,申请其他资源,即要想出一个办法,阻止进程在持有资源的同时申请其它资源。

方法一:在所有进程开始运行之前,必须一次性的申请其在整个运行过程中所需的全部资源,这样,该进程在整个运行期间便不会再提出资源请求,从而破坏了“请求”条件。系统在分配资源时,只要有一种资源不能满足进程的需要,即使其它所需的各资源都空闲也不分配给该进程,而让该进程等待,由于该进程在等待期间未占用任何资源,于是破坏了“保持”条件。

方法二:要求每个进程提出新的资源申请前,释放它所占有的资源。这样,一个进程在需要资源S时,需要先把它先前占有的资源R释放掉,然后才能提出对S的申请,即使它很快又要用到资源R。

两种协议比较:第二种协议优于第一种协议,因为第一种协议会造成资源的严重浪费,使资源利用率大大的降低,也会由于占据大量资源导致其它进程的饥饿问题。

(2)破坏不可抢占条件:允许对资源实行抢夺。

方式一:如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必须释放它最初占有的资源,如果有必要,可再次请求这些资源和另外的资源。

方式二:如果一个进程请求当前被另一个进程占有的资源,则操作系统可以抢占另一个进程,要求它释放资源,只有在任意两个进程的优先级都不相同的条件下,该方法才能预防死锁。

(3)破坏循环等待条件

对系统所有资源进行线性排序并赋予不同的序号,这样我们便可以规定进程在申请资源时必须按照序号递增的顺序进行资源的申请,当以后要申请时需检查要申请的资源的编号大于当前编号时,才能进行申请。

死锁避免和死锁预防的区别:

死锁预防是设法至少破坏产生死锁的四个必要条件之一, 严格的防止死锁的出现,而死锁避免则不那么严格的限制产生死锁的必要条件的存在,因为即使死锁的必要条件存在,也不一定发生死锁。死锁避免是在系统运行过程中注意避免死锁的最终发生。

死锁的解除
一旦检测出死锁,就应立即采取相应的措施,以解除死锁。死锁的解除主要有两种方法:
(1)抢占资源。从一个或多个进程中抢占足够数量的资源,分配给死锁进程,以解除死锁状态。
(2)终止或者撤销进程。终止或者撤销系统中的一个或多个死锁进程,直到打破循环环路,使系统从死锁状态解脱出来。

  1. C++中虚函数的作用和实现原理
    作用:C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数;在派生类中对基类定义的虚函数进行重写时,需要在派生类中声明该方法为虚方法。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态地调用属于子类的该函数,且这样的函数调用是无法在编译器期间确认的,而是在运行期确认,也叫做迟绑定。

底层实现原理:(要点是要答出虚函数表和虚函数表指针的作用。)
C++中虚函数是通过虚函数表和虚函数表指针实现,虚函数表是一个类的虚函数的地址表,用于索引类本身以及父类的虚函数的地址,假如子类重写了父类的虚函数,则对应在虚函数表中会把对应的虚函数替换为子类的函数的地址(子类中可以不是虚函数,但是必须同名);虚函数表指针存在于每个对象中(通常出于效率考虑,会放在对象的开始地址处),它指向对象所在类的虚函数表的地址;在多继承环境下,会存在多个虚函数表指针,分别指向对应不同基类的虚函数表。
12. 指针和引用的区别
1.指针和引用的定义和性质区别:
(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
(2)可以有const指针,但是没有const引用;
(3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(4)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
(5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(6)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;
(7)指针和引用的自增(++)运算意义不一样;
2.指针和引用作为函数参数进行传递时的区别:

  1. 指针作为参数进行传递:
    用指针传递参数,可以实现对实参进行改变的目的,是因为传递过来的是实参的地址,因此使用*a实际上是取存储实参的内存单元里的数据,即是对实参进行改变,因此可以达到目的。

  2. 将引用作为函数的参数进行传递。
    在将引用作为函数参数进行传递时,实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。

  3. C++多重继承的构造与析构的次序
    C++中多重继承的构造顺序是
    1)虚函数优先构造,其顺序是按那个继承的顺序来的,而不是按初始化列表来的
    2)然后构造普通的基类,其顺序也是按那个继承的顺序来的,不是按初始化列表来的
    3)最后构造成员对象
    4)构造派生类自己的函数体
    C++中多重继承的析构顺序是
    1)析构派生类自己的函数体
    2)析造成员对象
    3)然后析构普通的基类,其顺序也是按那个继承的顺序来的,也不是按初始化列表来的
    4)最后是那个虚基类

  4. extern C的作用详解
    extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

  5. static 和const关键字的用法总结
    static关键字至少有下列n个作用:
    (1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;

(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

(6)static函数中不能使用this关键字,因为this一般指当前对象,而static不属于任何对象。
(7)static函数不能声明为virtual虚函数,虚函数的主要作用是实现多态。
(8)static函数比普通函数访问的速度快很多。
const关键字至少有下列n个作用:
(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;

(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;

(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。
(6) const指针用法:
我们这里说两种指针,常量指针和指向常量的指针。如下:

指向常量的指针可以通过x改变,然后p中指向的值也改变了。
(7)const int * gr; 指针指向的数据只读,指针可以修改
int const * gr; 指针指向的数据只读,指针可以修改

int *const gre; 指针是只读的,指针指向的数据可以修改

const int * const g; 这两条语句指针和指针所指向的数据都是只读的。
int const * const t ;

总结:const 在 * 之前,指针指向的数据只读;const在 * 之后,指针只读。

  1. 冒泡排序
    冒泡排序可以算是最简单、最基础的排序算法了,它的基本思想是:重复的遍历(走过)待排序的一组数字(通常是列表形式),依次比较两个相邻的元素(数字),若它们的顺序错误则将它们调换一下位置,直至没有元素再需要交换为止。因为每遍历一次列表,最大(或最小)的元素会经过交换一点点”浮“到列表的一端(顶端),所以形象的称这个算法为冒泡算法。
    具体步骤

  2. 比较两个相邻元素,如果前一个比后一个大,则交换这两个相邻元素

  3. 从头至尾对每一对相邻元素进行步骤1的操作,完成1次对整个待排序数字列表的遍历后,最大的元素就放在了该列表的最后一个位置上了

  4. 对除最后一个元素的所有元素重复上述步骤,这第二次遍历后第二大的元素就也放在了正确的位置(整个列表的倒数第二位置上)

  5. 不断重复上述步骤,每次遍历都会将一个元素放在正确的位置上,从而下次遍历的元素也会随之减少一个,直至没有任何一对数字需要比较

  6. 快速排序(Quick Sort)----递归

  7. 从待排序列表(数组)中选择一个元素作为基准(pivot),这里我们选择最后一个元素元素

  8. 遍历列表,将所有小于基准的元素放在其前面,这样就可以将待排序列表分成两部分了

  9. 递归地对每个部分进行1、2操作,这里递归结束的条件是序列的大小为0或1,此时递归结束,排序就已经完成了

  10. 直接选择排序
    它的基本思想是:先在待排序列表中找到最小(大)的元素,把它放在起始位置作为已排序序列;然后,再从剩余待排序序列中找到最小(大)的元素放在已排序序列的末尾,以此类推,直至完毕。
    这个有点像暴力解决的意思,所以它的效率不高(最坏、最好、平均都一样很差)

具体步骤

  1. 初始状态整个待排序序列为无序序列,有序序列为空

  2. 每次遍历无序序列将最小元素交换到有序序列之后

  3. n-1趟遍历后排序完成

  4. 堆排序(Heap Sort)
    堆排序比之前几种排序稍微难理解一些,理解此算法,大家需要具备一些关于堆这种数据结构的知识储备。堆的结构相当于一个完全二叉树,最大堆满足下面的性质:父结点的值总大于它的孩子结点的值。

  5. 将待排序列表构造成一个最大堆,作为初始无序堆(即初始无序列表)

  6. 将堆顶元素(最大值)与堆尾元素互换

  7. 将该堆(无序区)尺寸缩小1,并对缩小后的堆重新调整为最大堆形式

  8. 重复上述步骤,直至堆(无序区)的尺寸变为1,此时排序完成

  9. 直接插入排序(Straight Insertion Sort)
    顾名思义,直接插入排序就是将未排序元素一个个的插入到已排序列表中,它的基本思想是:对于未排序元素,在已排序序列中从后向前扫描,找到相应位置把它插入进去;在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为新元素提供插入空间。

  10. 从第一个元素开始,默认该元素已被排好序

  11. 取出下一个元素,在已经排序的元素序列中从后向前扫描

  12. 如果该元素(已排序)大于新元素,将该元素移到下一位置

  13. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置

  14. 将新元素插入到该位置后

  15. 重复步骤2~5

  16. 归并排序(Merge Sort)
    归并排序的递归实现是算法设计中分治策略的典型应用,它的基本思想是:递归的将两个已排序的序列合并成一个序列。

  17. 申请空间,其大小为两个已经排序序列之和,该空间用来存放合并后的序列

  18. 设定两个指针,最初位置分别为两个已经排序序列的起始位置

  19. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

  20. 基数排序(Radix Sort)
    基数排序的基本思想是:将所有待比较正整数统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始进行基数为10的计数排序,一直到最高位计数排序完后,数列就变成一个有序序列(利用了计数排序的稳定性)。由此可知,基数排序、桶排序、计数排序三者关系很紧密,尤其计数排序!
    确定位数基数
    从低位到高位,每位看作一个桶,每个桶内进行计数排序
    最高位桶排序完成后,整个排序便完成了

  21. 顺序查找
    基本思想:顺序查找也称为线形查找,属于无序查找算法。从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功;若扫描结束仍没有找到关键字等于k的结点,表示查找失败。

  22. 二分查找
    基本思想:也称为是折半查找,属于有序查找算法。用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。

  23. 插值查找
     基本思想:基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。当然,差值查找也属于有序查找。

  24. 树表查找
    最简单的树表查找算法——二叉树查找算法。
    基本思想:二叉查找树是先对待查找的数据进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,查找最适合的范围。 这个算法的查找效率很高,但是如果使用这种查找方法要首先创建树。

二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree)或者是一棵空树,或者是具有下列性质的二叉树:
  1)若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2)若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3)任意节点的左、右子树也分别为二叉查找树。
  二叉查找树性质:对二叉查找树进行中序遍历,即可得到有序的数列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr_xiaosunyang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值