库函数模拟实现

1.模拟实现 strstr

[cpp] view plain copy
char* MyStrstr( const char* str1, const char* str2 ){   
   assert( NULL != str1 && NULL != str2 ); 
     
   while ('\0' != *str1){   
       const char* p1 = str1; 
       const char* p2 = str2; 
         
       while (('\0' != *p1) && ('\0' != *p2) && (*p1 == *p2)){ 
           ++p1; 
           ++p2; 
       } 
         
       if ('\0' == *p2) 
           return str1;   
         
       ++str1; 
   } 
     
   return NULL;   
}

2.模拟实现 strcpy

[cpp] view plain copy
char* MyStrcpy(char* dest, const char* src){   
   assert( NULL != dest && NULL != src ); 
     
   char* pAddress = dest; 
   while ('\0' != (*dest++ = *src++)) 
       ; 
 
   return pAddress; 
}

3.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?   
least = MIN(*p++, b); 
:

[cpp] view plain copy
#define MIN(A,B) ((A) <= (B) ? (A) : (B))

注意:

[cpp] view plain copy
MIN(*p++, b)会产生宏的副作用 
 
宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对“参数”进行的是一对一的替换     
程序员对宏定义的使用要非常小心,特别要注意两个问题: 
(1)谨慎地将宏定义中的“参数”和整个宏用用括弧括起来。所以,严格地讲,下述解答:   
#define MIN(A,B) (A) <= (B) ? (A) : (B) 
#define MIN(A,B) (A <= B ? A : B ) 
都应判0分 
(2)防止宏的副作用   
宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))对MIN(*p++, b)的作用结果是:     
((*p++) <= (b) ? (*p++) : (b))     
这个表达式会产生副作用,指针p会作2次++自增操作 
除此之外,另一个应该判0分的解答是: 
#define MIN(A,B) ((A) <= (B) ? (A) : (B));   
这个解答在宏定义的后面加“;”,显示编写者对宏的概念模糊不清,只能被无情地判0分并被面试官淘汰

4.为什么标准头文件都有类似以下的结构?

[cpp] view plain copy
#ifndef __INCvxWorksh 
#define __INCvxWorksh   
#ifdef __cplusplus 
extern "C" { 
#endif   
/*...*/   
#ifdef __cplusplus 
#endif   
#endif /* __INCvxWorksh */

头文件中的编译宏

[cpp] view plain copy
#ifndef __INCvxWorksh 
#define __INCvxWorksh 
#endif

的作用是防止被重复引用。   

作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为:   

void foo(int x, int y);   

该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。   

为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C++语言中就可以调用C语言的函数了。

C++支持函数重载,C语言不支持函数重载。C++为了支持函数重载,按照不同编译器的函数修饰规则,对函数名进行了处理,加入函数的参数类型进行修饰。这里函数是被C编译器编译的,编译后函数名没有进行修饰,所以要加extern"c"告诉编译器使用c的方式进行查找链接。


5.编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:

[cpp] view plain copy
class String{   
public:   
   String( const char* str = NULL ); // 普通构造函数   
   String(const String &other); // 拷贝构造函数   
   ~ String(void); // 析构函数   
   String & operator =(const String &other); // 赋值函数   
 
private:   
   char *m_data; // 用于保存字符串   
};


[cpp] view plain copy
// 普通构造函数 
String::String( const char* str ){ 
   if (NULL==str){ 
       m_data = new char[1]; // 对空字符串自动申请存放结束标志'\0'的空间 
       *m_data = '\0';   
   } 
   else{ 
       int length = strlen(str);   
       m_data = new char[length+1]; 
         
       strcpy(m_data, str);   
   } 
 
// String的析构函数 
String::~String( void ){ 
   delete [] m_data; // 或delete m_data; 
 
// 拷贝构造函数 
// 输入参数为const型   
String::String( const String& other ){ 
   int length = strlen( other.m_data );   
   m_data = new char[length+1];   
     
   strcpy( m_data, other.m_data );   
 
// 赋值函数 
// 输入参数为const型 
MyString& MyString::operator=( const MyString& str ){ 
   // 检查自赋值 
   if ( &str != this ){ 
       MyString strTemp( str ); 
 
       char* pTemp = strTemp.m_pData; 
       strTemp.m_pData = m_pData; 
       m_pData = pTemp; 
   } 
 
   // 返回本对象的引用 
   return *this; 
}

当类中包括指针类成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数


如果出函数作用域后还存在则可以返回引用, 不存在则不能返回引用,要返回临时对象



6.请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1(这里的大小端问题重点关注的是如何判断处理器是大端还是小端(笔试)。 大小端问题要完整掌握,还需与另一篇关注为什么有大小端,大小端的区别是什么的面试博客一起学习 )

大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

unsigned int value = 0x 12345678
Big-Endian: 低地址存放高位,如下:


高地址
  ---------------
   (0x78) -- 低位
   (0x56)
   (0x34)
   (0x12) -- 高位
  ---------------
  低地址


Little-Endian: 低地址存放低位,如下:


高地址
  ---------------
   (0x12) -- 高位
   (0x34)
   (0x56)
   (0x78) -- 低位
  --------------
低地址



e684 6c4e

在大端模式下,前32位应该这样读: e6 84 6c 4e(一个字节为8个二进制位,为2个16进制数字,所以这样表示 e6而不是6e)
记忆方法: 地址的增长顺序与值的增长顺序相反

在小端模式下,前32位应该这样读: 4e 6c 84 e6
记忆方法: 地址的增长顺序与值的增长顺序相同


[cpp] view plain copy
int CheckCPU( ){ 
   union w{ 
       int a; 
       char b; 
   } c; 
   c.a = 1; 
 
   return (1 == c.b); 
}

联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性获得了CPU对内存采用Little-endian还是Big-endian模式读写


联合体:各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值



7.写一个函数返回1+2+3+…+n的值(假定结果不会超过长整型变量的范围)


[cpp] view plain copy
long Sum( long n ){   
return ( (long)1 + n ) * n / 2; 
}


8.宏函数 --> 交换两个数

:

[cpp] view plain copy
#define SWAP( a, b ) \ 
do { \ 
   (a) = ((a)^(b)); \ 
   (b) = ((a)^(b)); \ 
   (a) = ((a)^(b)); \ 
}while (0)


1.SWAP和括号间不能有空格,否则会被错误替换
2.续航符后面不能有空格,否则编译器会报错,但是新的一行开头可以有空格



do { } while( 0 )意义:

辅助定义复杂的宏,避免引用的时候出错:
假设你需要定义这样一个宏:


#define DOSOMETHING( ) \
               foo1( ); \
               foo2( );
这个宏的本意是,当调用DOSOMETHING()时,函数foo1()和foo2()都会被调用。但是如果你在调用的时候这么写:
if (a>0)
    DOSOMETHING( );
因为宏在预处理的时候会直接被展开,你实际上写的代码是这个样子的:
if (a>0)
    foo1( );
foo2( );
这就出现了问题,因为无论a是否大于0,foo2()都会被执行,导致程序出错。


那么仅仅使用{}将foo1()和foo2()包起来行么?
我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},代码里就相当于这样写了:“{...};”,展开后就是这个样子:
if (a>0)
{
    foo1( );
    foo2( );
};
这样甚至不会编译通过。所以,很多人才采用了do {...}while (0);


#define DOSOMETHING( ) \
        do { \
          foo1( ); \
          foo2( ); \
        }while (0) \
 
if (a>0)
    DOSOMETHING( );
这样,宏被展开后,才会保留初始的语义。



8.模拟实现 strncpy


[cpp] view plain copy
char* MyStrncpy( char* dest, const char* src, size_t len ){   
   assert( NULL != dest && NULL != src ); 
 
   char* ret = dest;   
   while (0 != len--){ 
       if (0 != (*dest = *src)){ 
           ++dest;   
           ++src; 
       } 
   } 
     
   if (len > 0)   
       while (len--)   
           *dest++ = '\0';   
 
   return ret;   
}


在安全性方面,显然strncpy要比strcpy安全得多,strcpy无法控制拷贝的长度,不小心就会出现dest的大小无法容纳src的情况,就会出现越界的问题,程序就会崩溃。而strncpy就控制了拷贝的字符数避免了这类问题,但是要注意的是dest依然要注意要有足够的空间存放src,而且src 和 dest 所指的内存区域不能重叠,


[cpp] view plain copy
<span style="font-family:Arial, Helvetica, sans-serif;background-color:rgb(255,255,255);">9.不创建临时变量,交换两个数</span>


:

[cpp] view plain copy
a = a + b; 
b = a - b; 
a = a - b; 
// 
a = a * b; 
b = a / b; 
a = a / b; 
// 以上两种方法可能会溢出 
 
a = a ^ b; 
b = a ^ b; 
a = a ^ b; 
// 优化版本( 可读性低, 效率低 )


10.数组除一个数字出现一次外别的数字都出现了两次,求这个数字


[cpp] view plain copy
int FindOneTimeNumber( int* arr, size_t len ){ 
   assert( NULL != arr ); 
 
   int tmp = arr[0]; 
   for (int i = 1; i < len; ++i) 
       tmp = tmp ^ arr[i]; 
 
   return tmp; 
}


11.数组中有两个数字只出现了一次,其余数字都出现了两次,找出这个数字


[cpp] view plain copy
void FindNumbers( int* arr, int len, int* num1, int* num2 ){ 
   assert( NULL != arr && len > 0 ); 
 
   int tmp = arr[0]; 
   for (int i = 1; i < len; ++i) 
       tmp = tmp ^ arr[i]; 
 
   unsigned int flag = 1; 
   while (1){ 
       if (1 == ( tmp & flag )) 
           break; 
 
       flag <<= 1; 
   } 
 
   *num1 = 0; 
   *num2 = 0; 
   for (int i = 0; i < len; ++i){ 
       if (1 == ( arr[i] & flag )) 
           *num1 = *num1 ^ arr[i]; 
       else 
           *num2 = *num2 ^ arr[i]; 
   } 
}


12.每瓶汽水1元,两个空瓶换一瓶。现有20元,最多喝几瓶


[cpp] view plain copy
int DrinkNumbers( int money ){ 
   assert( money >= 0 ); 
 
   int sumDrinks = 0; 
   int emptyBottle = 0; 
 
   while (1){ 
       if (0 == money && emptyBottle <= 1) 
           break; 
         
       sumDrinks += money; 
 
       if (0 == money % 2) 
           money /= 2; 
       else{ 
           money /= 2; 
           ++emptyBottle; 
       } 
 
       if (2 == emptyBottle){ 
           ++sumDrinks; 
           emptyBottle = 1; 
       } 
   } 
 
   return sumDrinks; 
}


13.获取一个数二进制序列的奇数位和偶数位


[cpp] view plain copy
int a[32]; 
for (int i = 0; i < 32; ++i){ 
   a[i] = data % 2; 
   data /= 2; 
 
// 偶数 
for (int i = 1; i <= 31; i += 2) 
   cout << arr[i]; 
 
// 奇数 
for (int i = 0; i <= 20; i += 2) 
   cout << arr[i];


14.输入一个非负整数,返回组成它的数字之和


[cpp] view plain copy
int Sum( int num ){ 
   assert( num >= 0 ); 
 
   int sum = 0; 
   int m = 0; 
 
   while (0 != num){ 
       m = num % 10; 
       num /= 10; 
 
       sum += m; 
   } 
 
   return sum; 
}


15.

:

[cpp] view plain copy
// 1 -  1/2 +  1/3 -  1/4 + ... 1/100 
double sum = 0.0; 
int flag = 1; 
 
for (int i = 1; i <= 100; ++i){ 
   sum += ( 1 / (i * flag) ); 
   flag *= -1; 
 
// 求两个数的平均值,不用 (a+b) / 2 
average = (a & b) + ((a ^ b) >> 1); 
 
// 1-100中,9出现次数 
int times = 0; 
for (int i = 1; i <= 100; ++i){ 
   if (9 == i % 10) 
       ++times; 
 
   if (9 == i / 10) 
       ++times; 
 
// 将一个二进制数的奇偶位交换(中间的 或 换成 + 也可以) 
#define CHANGE(num) ( (((num) & (0X 55555555)) << 1) | ((num) & (0XAAAAAAAA) >> 1) ) 
 
// 判断一个字符串是否为另外一个字符串旋转之后的字符串 
bool IsRotate( const char* str1, const char* str2 ){ 
   assert( NULL != str1 && NULL != str2 ); 
 
   if (strlen( str1 ) != strlen( str2 )) 
       return false; 
 
   int newLen = strlen( str1 ) * 2 + 1; 
   char* pNew = (char*)malloc(newLen); 
   if (NULL == pNew) 
       return false; 
 
   strcpy( pNew, str1 ); 
   strcat( pNew, str1 ); 
 
   if (NULL == strstr( pNew, str2 )) 
       return false; 
 
   return true; 
 
// 实现一个函数,将一个数的二进制翻转并输出翻转后的值 
unsigned int ReverseBit( unsigned int val ){ 
   unsigned int a = 0; 
   unsigned int ret = 0; 
 
   for (int i = 0; i < 32; ++i){ 
        a = val & 1; 
        val >>= 1; 
 
        // 注意顺序,先移后或 
        ret <<= 1; 
        ret |= a; 
   } 
}


16.请写一个模板类,来求两个数中的最大值

注意:求两个"数"的最大值,若两个"数"不是内置类型,而是自定义类型比较大小。则1.看原自定义类型是否重载operator <( )运算符 2.仿函数



[cpp] view plain copy
template <typename T> 
class MaxNumber{ 
public: 
   MaxNumber( ) 
       : _a( a ) 
       , _b( b ) 
   {} 
 
   T Max( ){ 
       return _a > _b ? _a : _b; 
   } 
 
private: 
   T _a; 
   T _b; 
}; 
 
int main( ){ 
   int a; 
   int b; 
   cin >> a >> b; 
 
   MaxNumber<int> m( a, b ); 
   m.Max( ); 
 
   return 0; 
}


17.写一个函数来判断一个字符串是否是对称的


[cpp] view plain copy
bool Fun( const char* str ){ 
   bool ret = true; 
     
   if (NULL == str || ('\0' == *str)) 
       return ret; 
 
   char* pBegin = str; 
   char* pEnd = str + strlen(str) - 1; 
 
   // 注意此处不能用 pBegin != pEnd 判断. 想想字符串为偶数个字符的情形(不包括'\0'),是否两个指针刚好错过了. 
   while (pBegin < pEnd){ 
       if (*pBegin == *pEnd){ 
           ++pBegin; 
           --pEnd; 
       } 
       else{ 
           ret = false; 
           break; 
       } 
   } 
 
   return ret; 
}


18.二分查找的算法



[cpp] view plain copy
int BinarySearch( int* arr, int len, int key ){ 
   assert( NULL != arr && len > 0 ); 
 
   int left = 0;   
   int right = len - 1;   
   int mid = 0; 
 
   while (left<=right){   
       mid = left + ( (right-left)>>1 );// 不直接用 (left+right)/2 是 防止越界 和 提高效率   
 
       if (arr[mid] < key)   
           left = mid + 1;   
       else if (arr[mid] > key)   
           right = mid - 1;   
       else     
           return mid; 
   } 
 
   return -1;   
}


right = n - 1 => while (left <= right) (利用只有一个元素时情景去想, 如果是 left(0) < rightr(1 - 1 == 0) 循环根本进不去。 所以是<=) => right = middle - 1(middle索引的元素已经被判断,又因为是 left <= right, 所以 right = mid - 1 如果是 right = mid, 这mid索引这个位置元素又会在下次循环被遍历一次); 


right = n => while (left < right)(数组只有一个元素 0 < 1 --> 循环进去一次,<=循环进去两次) => right = middle(middle已经被遍历, 又因为left < right, 若right = middle - 1则middle - 1那个元素被略过, 而right = middle -> left < right --> middle不会被重复遍历,middle - 1的元素也不会被略过); 
middle = ((left+right) >> 1); 这样的话 left 与 right 的值比较大的时候,其和可能溢出。 --> middle = left + ( (right - left) >> 1 )



19.快速排序

[cpp] view plain copy
template <typename T> 
void QuickSort( vector<T>& a ){ 
   QuickSort( a, 0, a.size( )-1 ); 
 
template <typename T> 
const T& Median3( vector<T>&a, int left, int right ){ 
   int center = left + ((right - left) >> 1); 
 
   if (a[center] < a[left]) 
       swap( a[left], a[center] ); 
   if (a[right] < a[left]) 
       swap( a[left], a[right] ); 
   if (a[right] < a[center]) 
       swap( a[center], a[right] ); 
 
   swap( a[center], a[right - 1] ); 
   return a[right - 1]; 
 
template <typename T> 
void QuickSort( vector<T>& a, int left, int right ){ 
   if (left + 10 <= right){ 
       T pivot = median3( a, left, right ); 
 
       int i = left; 
       int j = right - 1; 
       for ( ; ; ){ 
           while (a[++i] < pivot) 
               ; 
 
           while (a[--j] > pivot) 
               ; 
 
           if ( i < j ) 
               swap( a[i], a[j] ); 
           else 
               break; 
       } 
 
       swap( a[i], a[right-1] ); 
 
       QuickSort( a, left, i - 1 ); 
       QuickSort( a, i + 1, right ); 
   } 
   else 
       InsertSort( a, left, right ); 
}


20.插入排序

插入排序由N-1趟排序组成。对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。事实:位置0到位置p-1上的元素是已经排过序的。

在第p趟,我们将位置p上的元素向左移动至它在前p+1个元素中的正确位置上。

[cpp] view plain copy
template <typename T> 
void InsertSort( vector<T>& a ){ 
   int j; 
 
   for (int p = 1; p < a.size( ); ++p){ 
       T tmp = a[p]; 
       for (j = p; j > 0 && tmp < a[j-1]; ++j) 
           a[j] = a[j-1]; 
 
       a[j] = tmp; 
   } 
}


21.冒泡、选择



[cpp] view plain copy
void BubbleSort( void ){   
   int i = 0;   
   int j = 0;   
   
   for (i = 0; i < n - 1; ++i){   
       for ( j = 0; j < n - i - 1; ++j ){   
               /* */   
           } 
   }   
}   
 
void SelectSort( void ){   
   int maxIndex = 0;   
   int i = 0;   
   int j = 0;   
   
   for (i = 0; i < n - 1; ++i) {   
       maxIndex = i;   
   
       for ( j = i; j < n - 1; ++j )// j 从 i 开始;下来依次为 j < n - 1; arr[j + 1];//j 从 i + 1 开始,则依次为 j < n; arr[j] 
       {   
           if ( arr[j + 1] > arr[maxIndex] )   
               maxIndex = j + 1; 
       }   
   
       swap ( arr[i], arr[maxIndex] );   
   }   
}


22.堆排序



[cpp] view plain copy
// 根据数组构建大堆 
void HeapAdjust( int* arr, int i, int len ){ 
   int child; 
   int tmp; 
 
   for (; 2 * i + 1 < len; i = child){ 
       // 子结点 = 2 * 父结点 + 1 
       child = 2 * i + 1; 
 
       // 得到子结点中数值较大的结点 
       if (child < len-1 && arr[child+1] > arr[child]) 
           ++child; 
 
       // 如果较大的子结点值大于父结点的值那么把较大的子结点往上移动,替换它的父结点 
       if (arr[i] < arr[child]){ 
           tmp = arr[i]; 
           arr[i] = arr[child]; 
           arr[child] = tmp;   
       } 
       else   
           break;   
   } 
 
//堆排序算法 
void HeapSort( int* arr, int len ){ 
   int i; 
 
   // length/ 2-1是最后一个非叶节点 
   // 构建大堆 
   for (i = len/ 2-1; i >= 0; --i) 
       HeapAdjust( arr, i, len ); 
 
   // 从最后一个元素开始对序列进行调整,不断缩小调整范围直到第一个元素 
   for (i = len-1; i > 0; --i){ 
       // 把第一个元素和当前的最后一个元素交换 
       // 保证当前最后一个位置的元素都是现在这个序列之中最大的元素 
       arr[i] = arr[0] ^ arr[i]; 
       arr[0] = arr[0] ^ arr[i]; 
       arr[i] = arr[0] ^ arr[i]; 
 
       // 不断缩小调整范围,每一次调整完毕后保证第一个元素是当前序列的最大值 
       HeapAdjust( arr, 0, i ); 
   } 
}


23.希尔排序(插入排序的改进版本),希尔排序的步长现在还没有最好的选择,不过已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,...)

操作步骤:


初始时,有一个大小为 10 的无序序列。
(1)在第一趟排序中,我们不妨设 gap1 = N / 2 = 5,即相隔距离为 5 的元素组成一组,可以分为 5 组。
(2)接下来,按照直接插入排序的方法对每个组进行排序。
在第二趟排序中,我们把上次的 gap 缩小一半,即 gap2 = gap1 / 2 = 2 (取整数)。这样每相隔距离为 2 的元素组成一组,可以分为 2 组。
(3)按照直接插入排序的方法对每个组进行排序。
(4)在第三趟排序中,再次把 gap 缩小一半,即gap3 = gap2 / 2 = 1。 这样相隔距离为 1 的元素组成一组,即只有一组。
(5)按照直接插入排序的方法对每个组进行排序。此时,排序已经结束。

时间复杂度:


最好情况:由于希尔排序的好坏和步长gap的选择有很多关系,因此,目前还没有得出最好的步长如何选择(现在有些比较好的选择了,但不确定是否是最好的)。所以,不知道最好的情况下的算法时间复杂度。
最坏情况下:O(N*N)


空间复杂度:


由直接插入排序算法可知,我们在排序过程中,需要一个临时变量存储要插入的值,所以空间复杂度为O(1)。


算法稳定性


希尔排序中相等数据可能会交换位置,所以希尔排序是不稳定的算法。
 在使用 增量k的一趟排序后,对于每一个i,我们有 a[i] <= a[i + k]。所有相隔k的元素都被排序。希尔排序一个重要性质:一个k排序的文件保持它的k排序性。如果情况不是这样,该排序就没有意义了,因为后面各趟排序会打乱前面各趟排序的成果。

k排序的一般做法:对于i,i+k,i+2k...中的每一个位置,都把其上的元素放到前面序列中的正确位置上。

一趟k排序作用是对k个独立的子数组进行一次插入排序。

[cpp] view plain copy
template <typename T> 
void ShellSort( vector<T>& a ){ 
   for (int gap = a.size( ) / 2; gap > 0; gap /= 2) 
       for (int i = gap; i < a.size( ); ++i){ 
           T tmp = a[i]; 
           int j = i; 
 
           for (; j >= gap && tmp < a[j - gap]; j-= gap) 
               a[j] = a[j - gap]; 
 
           a[j] = tmp; 
       } 
}


24.归并排序

合并两个已排序的表时间是线性的,最多进行N-1次比较,N是两个表元素的总数,因为每次比较都把一个元素添加到临时数组中,但最后一次比较除外,它至少添加两个元素。

如果对Merge的每个递归调用均局部声明一个临时数组,那么在任一时刻就可能有 logN个临时数组处在活动期。 由于Merge是MergeSort的最后一行,因此在任一时刻只需要一个临时数组活动,而且这个临时数组可以在MergeSort驱动程序中建立。还可以使用临时数组的任意部分。我们将使用与输入数组a相同的部分。

[cpp] view plain copy
template <typename T> 
void MergeSort( vector<T>& a ){ 
   vector<T> tmpArr( a.size( ) ); 
 
   MergeSort( a, tmpArr, 0, a.size( ) - 1 ); 
 
template <typename T> 
void MergeSort( vector<T>& a, vector<T>& tmpArr, int left, int right ){ 
   if (left < right){ 
       int center = ( left + right ) / 2; 
       MergeSort( a, tmpArr, left, center ); 
       MergeSort( a, tmpArr, left+1, right ); 
       Merge( a, tmpArr, left, center+1, right ); 
   } 
 
template <typename T> 
void Merge( vector<T>& a, vector<T>& tmpArr, int leftPos, int rightPos, int rightEnd ){ 
   int leftEnd = rightPos - 1; 
   int tmpPos = leftPos; 
   int num = rightPos - leftPos + 1; 
 
   while (leftPos <= leftEnd && rightPos <= rightEnd) 
       if (a[leftPos] <= a[rightPos]) 
           tmpArr[tmpPos++] = a[leftPos++]; 
       else 
           tmpArr[tmpPos++] = a[rightPos++]; 
 
   while (leftPos <= leftEnd) 
       tmpArr[tmpPos++] = a[leftPos++]; 
 
   while (rightPos <= rightEnd) 
       tmpArr[tmpPos++] = a[rightEnd++]; 
 
   for (int i = 0; i < num; i++, rightEnd--) 
       a[rightEnd] = tmpArr[rightEnd]; 
}


25. 实现时间复杂度为O(nlogn)的链表排序算法




26.模拟实现strlen

[cpp] view plain copy
int MyStrlen( const char* str ){   
   assert( NULL != str );   
     
   int len = 0; 
   while ('\0' != *str++) 
       len++; 
     
   return len;   
}


不用变量版本:


[cpp] view plain copy
int MyStrlen( const char* src ){ 
   assert( NULL != src ); 
 
   if ('\0' == *src) 
       return 0; 
   else 
       return ( 1 + MyStrlen( ++str ) ); 
}


27.模拟实现memcpy、memmove和memset

strcpy和memcpy主要有以下3方面的区别。
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容(内存中的内容),例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

[cpp] view plain copy
void* MyMemcpy( void* dest, const void* src, size_t n ){   
   assert( NULL != dest );   
   assert( NULL != src ); 
 
   char* pDest = (char*)dest;   
   const char* pSrc = (const char*)src; 
   while (n--) 
       *pDest++ = *pSrc++; 
 
   return dest;   
}


[cpp] view plain copy
void* MyMemmove( void* dest, const void* src, size_t count ){   
   assert( NULL != dest );   
   assert( NULL != src );   
 
   char* pDest = (char*)dest;   
   const char* pSrc = (const char*)src;   
   
   if (pDest <= pSrc || pSrc + count <= pDest)// 正常情况下,从前往后拷贝     
       while (count--)   
           *pDest++ = *pSrc++;   
   else   
       while (count--)   
           *(pDest + count - 1) = *(pSrc + count - 1); 
   
   return dest;   
}


[cpp] view plain copy
void* MyMemset( void* dest, int c, size_t count ) {   
   assert( NULL != dest ); 
 
   char* tmp = (char*)dest; 
   while (count--){   
       *tmp = (char)c;   
       tmp++; 
   } 
 
   return dest;   
}


memcpy
内存拷贝函数
source和destin所指的内存区域可能重叠,但是如果source和destin所指的内存区域重叠,那么这个函数并不能够确保source所在重叠区域在拷贝之前不被覆盖。而使用memmove可以用来处理重叠区域。函数返回指向destin的指针。


memmove
内存移动函数
如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中。但复制后src内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。memmove函数能够反向拷贝,不用在意memcpy函数会遇到的问题。


memset
将dest中从当前位置开始的count个字节,用 c 替换并返回 dest 。

在strcpy,strncpy,memcpy和memmove这四个库函数中,安全性是递增的,前三个函数均没有考虑到内存重叠的问题,所以相对来说memmove函数的安全性最高。



28.从键盘输入一个十进制正整数,将其转换成八进制后输出

[cpp] view plain copy
#include <stdlib.h> 
#include <stdio.h> 
 
 
int main( void ){ 
   int number = 123456; 
   char string[25]; 
     
   itoa( number, string, 8 ); 
   printf( "string = %s\n", string ); 
     
   return 0; 
 
 
#include <stdio.h> 
#include <math.h> 
 
 
int dtoe( int a ){   
   if (a < 8)   
       return a; 
   else   
       return ( dtoe(a / 8) * 10 + a % 8 );   
 
   return 0; 
}   
 
 
int main( void ){   
   int tmp; 
 
   scanf( "%d", &tmp );   
   tmp = dtoe( tmp ); 
   printf( "%d\n", tmp ); 
 
   return 0;   
}

猜你喜欢

转载自blog.csdn.net/fujibao/article/details/79875479